Skip to content

使い方 — カスタムアカウント選択 UI

prompt=select_account には 2 つの関心事があります。

  • セッションの意味論: ブラウザが複数の有効アカウントを含む chooser group を持ち、選択されたセッションが次の sub を決める
  • 描画面: アカウント一覧を表示し、選択された SessionID を POST するページ

マルチアカウント選択 は前者を扱います。このページは後者、つまりブランド付きのサーバ描画のアカウント選択画面を持ちつつ、state、CSRF、最後の Sessions.Switch は OP に任せるための op.WithChooserUI を扱います。

ソース: examples/12-custom-chooser-ui は、既定の HTML interaction ドライバで op.WithChooserUI を使う例です。JSON ドライバ / SPA 経路は examples/13-multi-account と対比してください。

使いどころ

目的使うもの
同梱 chooser をそのまま使うオプション不要。既定 HTML ドライバが描画
chooser の HTML / 文言 / レイアウトだけ変え、サーバ描画に留めるop.WithChooserUI(op.ChooserUI{Template: tmpl})
chooser を SPA の中で描画するop.WithSPAUI または interaction.JSONDriver
アカウントのグループ化や切替のロジックを変えるテンプレートではなく session store / authenticator 側

WithChooserUI は意図的に狭い差し込み口です。差し替えるのはテンプレートだけで、テンプレートが任意の subject を選んだり、セッションを発行したり、OP の状態遷移を迂回したりする経路ではありません。

テンプレートの契約

テンプレートには interaction.ChooserTemplateData が渡されます。主なフィールドは次の通りです。

フィールド用途
Accountschooser group 内の有効セッション。SessionID、subject、表示ラベル、auth time などを含む
StateRefそのまま返す不透明な interaction state 参照
CSRFTokenPOST 時に OP が検証するトークン
SessionIDField選択アカウント用に OP が期待するフォームフィールド名
SubmitMethod通常は POST
SubmitActioninteraction endpoint URL
AddAccountURL別アカウント追加のために prompt=login 経路を開始する URL

最小形は次のようになります。

go
tmpl := template.Must(template.New("chooser").Parse(`
{{range .Accounts}}
  <form method="{{$.SubmitMethod}}" action="{{$.SubmitAction}}">
    <input type="hidden" name="state_ref" value="{{$.StateRef}}">
    <input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
    <input type="hidden" name="{{$.SessionIDField}}" value="{{.SessionID}}">
    <button type="submit">Continue as {{.DisplayName}}</button>
  </form>
{{end}}
<a href="{{.AddAccountURL}}">Sign in to another account</a>
`))

provider, err := op.New(
  /* 必須オプション */
  op.WithInteractionDriver(interaction.HTMLDriver{}),
  op.WithChooserUI(op.ChooserUI{Template: tmpl}),
)

フィールド名は OP との契約です。state_refcsrf_token、動的な SessionIDField は送信フォームに残してください。

op.ChooserUI にも ContentSecurityPolicy field があります。空文字は厳格な既定値(default-src 'none'; style-src 'none'; frame-ancestors 'none'; base-uri 'none')を使います。独自 policy は構築時に interaction.NormalizeCSP で正規化され、form-action は禁止、frame-ancestorsbase-uri は指定する場合も厳密に 'none' でなければなりません。欠けている保護 directive は自動追加され、不正な policy は interaction.ErrCSPNotPermitted を包んだ op.New エラーになります。テンプレートに必要な asset directive だけを追加し、trust boundary 上の理由がない限り script は無効のままにしてください。

Flow

chooser — リクエストから切り替えまで
prompt=select_account を伴うリクエストを受けると、OP は chooser group を読み込んで自前のテンプレートに渡します。送信された内容には CSRF トークンと state 参照が含まれ、セッションを切り替える前に両方を検証します。ブラウザユーザの手元OPgo-oidc-providerchooser テンプレート組み込み側の実装1GET /authorize · prompt=select_account2cookie が指す chooser group を読み込む3ChooserTemplateData を描画する4アカウント一覧および CSRFToken と StateRef5POST SubmitAction · session_id6CSRFToken と StateRef を検証する切り替えは、その後で行う7Sessions.Switch(group, session_id)8code 付きで RP へ 302
自前のテンプレートが持つのはマークアップだけです。送り返す 2 つの不透明な値が、提出された session_id を「このブラウザに今提示されたもの」だけに限定しています。

テンプレートは切替そのものを実行しません。選択されたセッション識別子を OP に返すだけです。

SPA interaction との優先関係

op.WithSPAUI を使う場合、chooser の描画は JSON の状態取得を通じて SPA が受け持ちます。WithSPAUIWithChooserUI が同時に設定されている場合、SPA 経路が優先され、chooser テンプレートは起動時の警告付きで無視されます。配備ごとに UI の所有者を 1 つに絞ってください。

UI の所有者オプション
OP によるサーバ描画 HTMLop.WithChooserUI
OP がマウントする SPA の入口op.WithSPAUI
自前ルータが SPA を配信op.WithInteractionDriver(interaction.JSONDriver{})

本番運用メモ

  • テンプレートは起動時に一度だけ parse し、リクエストごとに parse しない。
  • CSP は厳しく保つ。テンプレートデータには RP 由来のクライアント表示名などが入り得るため、html/template のエスケープに乗せ、インラインスクリプトを避ける。
  • SessionID は不透明な値として扱う。OP はそれが有効な chooser group に属するかを検証する。
  • 「アカウント追加」リンクは提供された AddAccountURL を使う。そうすれば次のログインが既存 chooser group に加わる。

続きはこちら