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_ref、csrf_token、動的な SessionIDField は送信フォームに残してください。

op.ChooserUI にも ContentSecurityPolicy field があります。空文字は厳格な既定値(default-src 'none'; style-src 'none'; frame-ancestors 'none'; base-uri 'none')を使います。独自 policy は構築時に interaction.NormalizeCSP で正規化され、form-action は禁止、frame-ancestors と base-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 が受け持ちます。WithSPAUI と WithChooserUI が同時に設定されている場合、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 に加わる。

続きはこちら ​