同意
同意(consent) とは、ユーザが「このアプリにこの scope を渡してよい」と明示する意思表示です。OIDC Core 1.0 §3.1.2.4 はタイミングを定めておらず、情報を渡す前に OP が認可の判断を得ることだけを要求しています。本ライブラリは、クライアントが scope 集合を初めて要求したときに同意画面を出し、その回答を覚えます。
このページで触れる仕様
- OpenID Connect Core 1.0 — §3.1.2.4(同意)、§3.1.2.1(
prompt) - RFC 6749 — §3.3(scope)、§6(refresh)
- RFC 7009 — トークン失効(§2.1 のカスケード)
メンタルモデル
- grant とは「ユーザ U がクライアント C に scope 集合 S を認めた」という記録で、
(subject, client_id)の組で引く。 - 既存の grant でカバーされない scope を要する authorize フローの最初の 1 回で 同意画面 が表示される。
- 同じ scope 集合内に収まる以降のフローは、プロンプトを スキップ する。
- 新しい scope の追加(scope の差分)は、その差分だけを尋ねるプロンプトを再度呼び出す。
- grant を失効させると記録が削除され、次の authorize フローでは再びプロンプトが出る。
同意画面が表示されるタイミング
OP は authorize フローのたびに、図の順で次の 4 つのケースを評価します。
| ケース | 挙動 |
|---|---|
初回 grant — その (subject, client_id) ペアに対する Grant が存在しない。 | プロンプト。ユーザは scope の全リストを目にします。送信時、本ライブラリは承認された scope を含む Grant 行を書き込みます。 |
scope の差分 — Grant は存在するが、要求された scope 集合に「過去に承認されていない scope」が含まれる。 | プロンプト。差分が強調表示されます。ユーザは差分のみ承認、差分を拒否、全体を拒否のいずれかを選べます。承認時に行が更新されます。 |
強制再プロンプト — リクエストが prompt=consent を持つ。 | grant の状態に関係なくプロンプト。OIDC §3.1.2.1 が要求する挙動です。 |
既存 grant が要求をカバー — Grant 行がすべての要求 scope と、要求されたすべての authorization_details 要素をカバーしており、prompt が省略、none、または login(consent ではない)。 | スキップ。本ライブラリは認可コード発行に直接進みます。スキップ専用の監査イベントはありません。 |
grant は同意画面から作られるものに限りません。device code、CIBA、token exchange の引き換えでも、対象クライアントに対する store.Grant が記録されます。/auth はこの grant も同意として扱うため、同じ scope を求める後続の要求では同意画面が出ません。
同意をスキップしても、認証ルールはスキップされません。既存セッションからコードを発行する前に、/auth は LoginFlow のルールと Decider を、チェーンが Primary を完了した直後であるかのように評価します。RuleClient や RuleScope による step-up が該当すれば、その step を先に実行します。prompt=none の場合は代わりに interaction_required を返します。この判定は、grant がカバーしている経路と、後述のファーストパーティのスキップの両方に適用されます。
prompt=none、prompt=login、prompt=consent の違い
prompt=none— 「ユーザがログイン済みで同意も整っているなら、フローを静かに完了させる。さもなくばエラー(login_required、consent_required、interaction_required)を返す」。SPA の静かな再認証で使う。prompt=login— 「有効なセッションがあっても、ユーザに再認証させる」。これだけでは同意の再プロンプトは強制されない。prompt=consent— 「grant が scope をカバーしていても、同意画面を強制する」。RP が機微な操作の前にユーザに再確認させたいときに有用。
本ライブラリは OIDC Core 1.0 §3.1.2.1 に沿ってこれらを適用します。prompt=none と prompt=login の併用は invalid_request で拒否します。
終端 subject に対するカバー判定の再確認
最初のカバー判定は authorize 開始時点で参照できる subject を使います。prompt=login、max_age の期限切れ、RFC 9470 の step-up では、その後の認証で別の subject に結び付くことがあります。アカウント選択画面によるアカウント切替も同様です。認可コードを発行する前に、本ライブラリは要求された scope と authorization_details を終端 subject 自身の grant に対してもう一度判定します。その grant が要求をカバーしなければ、最初の subject の grant で認可することはなく、終端 subject に対する同意を取得するか、コードを発行せずにフローを終了します。
同意のみで発行する場合の認証コンテキスト
認証要素を実行せず、有効なセッションが残っている場合、認可コードと grant はそのセッションの auth_time、acr、amr を保持します。同意画面への回答時刻で auth_time を置き換えたり、空の認証要素の集合で acr や amr を消したりしません。セッションの期限切れ、別タブでのログアウト、アカウント切替などで有効な認証コンテキストが残っていない場合は、存在しない認証コンテキストを作らず login_required で拒否します。
ファーストパーティクライアント
組み込み側によっては、主力 web クライアント、社内管理コンソール、自社モバイルアプリのような信頼された少数のアプリを運用しており、同意画面がユーザの手間にしかならないことがあります。op.WithFirstPartyClients(ids ...string) は、指定した client_id について、Grant の有無にかかわらず同意画面を スキップ します。列挙した ID は op.WithStaticClients でも登録されている必要があります。スキップの対象は静的シードで登録したクライアントと管理者が作成したクライアントだけで、動的登録されたクライアントには適用されません。
スキップが働くのは、次の条件がすべて満たされるときだけです。
- ユーザにすでにセッションがある、または要求が先にサインインを必要とする(セッションなし、
prompt=login、max_age、ACR の step-up)。後者では、grant はそのサインインで確定した subject に紐づきます。スキップが subject を新たに作ることはありません。アカウント選択(prompt=select_account)と新規登録(prompt=create)では画面を出します。 - 要求が
prompt=consentを持たない。prompt=consentは常に画面を出します。 - scope に
offline_accessを含まず、authorization_detailsも Grant Management の操作も含まない。これらは常に明示的な画面を出します。 - ブラウザが
Sec-Fetch-Site: same-originを送る、またはsame-siteでOriginかRefererがredirect_uriの origin と一致する。cross-site のナビゲーションではスキップしません。
あわせて 2 点あります。
- スキップは記録される。 本ライブラリは
Grant行を通常通り書き込み、audit.ConsentGrantedFirstPartyを発火します。「このユーザは結局どの scope を承認したのか」という監査の問いには答えられます。変わるのは、ユーザが画面を見たかどうかだけです。 - FAPI 2.0 プロファイルでは使えない。
WithFirstPartyClientsをprofile.FAPI2Baselineやprofile.FAPI2MessageSigningと併用すると、op.Newが失敗します。
完全な設定(操作者側の scope カタログを含む)は ファーストパーティ同意スキップ を参照。
ファーストパーティは操作者が宣言するもので、ユーザが宣言するものではない
ユーザは同意フローで「これはファーストパーティアプリです」とは見せられません。信頼の根拠は、操作者が op.WithFirstPartyClients(...) に client_id を列挙していることです。コードパスをエンドツーエンドで管理していないクライアントを列挙してはなりません — そうするとスキップが「告知のない scope 付与」に変わってしまいます。
同意 UI の呼び出し方
プロンプトが必要になると、本ライブラリは組み込み側の同意テンプレート(op.HTMLUI の Consent フィールドを op.WithUI に渡して登録)に制御を渡します。テンプレートが HTML をレンダリングし、本ライブラリが state、CSRF、__Host-oidc_csrf の double-submit cookie、永続化を担当します。
op.WithUI(op.HTMLUI{
Consent: op.PageTemplate{
Template: myConsentTemplate, // *template.Template
},
})テンプレートには、クライアント(ID、表示名、ロゴなどのクライアントメタデータ)、要求 scope(既存 grant に対する差分があればそれも)、フォームに埋め込む CSRF トークンが渡されます。Required が付いた scope はユーザが外せません。PageTemplate.ContentSecurityPolicy は同意ページの CSP を指定します。既定ではすべてのサブリソースを禁止するため、スタイルシート、ロゴ、フォントを読み込むテンプレートは、その origin を宣言してください。フォームの POST を受けて、本ライブラリは CSRF を検証し、承認された scope 集合を解析し、Grant 行を書き込み、authorize フローを継続します。改竄された送信や古い送信には 4xx を返し、画面をキャンセルすると access_denied でリダイレクトします。
RFC 9396 の authorization_details は要素の完全一致でカバー判定します。新しい要素やメンバの変更があれば、scope 集合が既にカバーされていても同意を求めます。組み込みの同意契約が扱うのは scope の承認で、detail ごとの承認・絞り込み UI はありません。通常の同意が完了すると、要求された detail は grant と一緒に永続化されます。
クライアント側で同意を描画する SPA(ログインフォームをすでに扱っている React アプリなど)では、SPA の入口と静的アセットも OP にマウントさせたい場合に op.WithUI(op.SPAUI{...}) を使います。このモードではブラウザは LoginMount/{uid} に着地し、SPA はプロンプトの状態を LoginMount/state/{uid} から取得します。SPA 本体を自前のルータで配信する場合は、低レベルな op.WithUI(op.DriverUI{Driver: interaction.JSONDriver{}}) 経路を使います。この場合、状態取得エンドポイントは /interaction/{uid} のままです。
WithUI が受け取るモードは 1 つなので、SPA と同意テンプレートは組み合わせではなく択一です。route 形と使い分けは SPA のカスタム対話画面 を参照してください。
同意の失効
ユーザまたは運用者は、いつでも同意を失効させられます。経路は 3 つあり、影響の範囲が異なります。
| 経路 | 失効させるもの | grant の記録 |
|---|---|---|
トークン 1 つを指定した /revoke(RFC 7009) | そのトークン。リフレッシュトークンなら、そのローテーション連鎖全体と、grant に紐づくアクセストークン。grant の別のリフレッシュ連鎖と未引き換えのコードは有効なまま。 | 残る。次の authorize フローは引き続き同意をスキップする。 |
Grant Management の失効(grant に対する DELETE) | grant に紐づくすべてのアクセストークンとリフレッシュトークン。 | 削除される。次の authorize フローはカバーする grant を見つけられず、再び同意を求める。 |
Provider.RevokeSubject(ctx, subject) | subject が持つすべての grant について、上と同じ。ユーザの無効化やプロビジョニング解除向け。 | 削除される。 |
3 つは同じ失効処理を共有するため、JWT アクセストークン、opaque アクセストークン、リフレッシュトークンは入口によらず同じ方法で失効します。device code、CIBA、token exchange の引き換えで発行されたトークンは grant の配下にあるため、これらの経路でも失効します。カスタム grant が発行した認証情報は、ハンドラが grant を記録しない限り対象外です。
Grant Management による失効が成功すると grant_management.revoked を発火します。一般的な grant / トークン失効に対して専用の consent.revoked イベントは発火しません。end-session カスケード(セッションとログアウト)も同じ失効処理を使いますが、grant の記録は残します。
監査イベント
同意フローは、op.WithAuditLogger 経由で 2 つの同意関連監査イベントを発火します。
| イベント | 発火タイミング |
|---|---|
audit.ConsentGranted | ユーザが同意フォームを送信し、Grant 行が書き込まれた。 |
audit.ConsentGrantedFirstParty | ファーストパーティの自動同意が適用された(プロンプトは出していない)。 |
audit.ConsentGranted は scope や authorization_details の要素を追加する通常の再同意も表します。本ライブラリは audit.ConsentGrantedDelta、audit.ConsentSkippedExisting、audit.ConsentRevoked を発火しません。Grant Management による失効の成功は audit.GrantManagementRevoked で報告されます。
発火する同意イベントを組み合わせると、SOC ダッシュボードで「ユーザが明示的に同意画面をクリックした」と「操作者が事前承認した静かな経路でこのクライアントが同意を得た」を区別できます — 特定の Grant 行がなぜ存在するのかを振り返る際に有用です。
次に読む
- ユースケース: ファーストパーティクライアント — 操作者側の scope カタログ、
op.WithFirstPartyClients、監査の方針。 - ユースケース: カスタム同意 UI —
op.HTMLUI{Consent: ...}の設定、テンプレート契約、CSRF の扱い。 - リファレンス: 監査イベント — 本ライブラリが発火するすべての監査イベントと付随する追加フィールド。