アーキテクチャ概観
op.New(...) は http.ServeMux を内部に持つ http.Handler を返します。本ページではリクエスト到着からレスポンスまでの間に何が走るか — 関わるパッケージ、検証の順序、組み込み側が制御するストレージの差し込み口 — を整理します。
パッケージ構成
op/ ← 公開 API 表面(組み込み側はここを import)
op/profile/ ← FAPI 2.0 / 将来のプロファイル
op/feature/ ← PAR / DPoP / mTLS / introspect / revoke / DCR / JAR
op/grant/ ← authorization_code、refresh_token、client_credentials
op/store/ ← Store interface(サブストアの集合)+ contract test suite
op/storeadapter/{inmem,sql,redis,composite}
op/interaction/ ← ログイン UI 用 HTML / JSON ドライバの差し込み口
internal/ ← 外部からは import 不可(Go の可視性)
authn/ ← LoginFlow オーケストレータ、Authenticator runtime
authorize、parendpoint、tokenendpoint、userinfo、
introspectendpoint、revokeendpoint、registrationendpoint、
endsession、backchannel
jose、jwks、keys ← 署名 / 検証 / 鍵セット
jar、dpop、mtls、pkce、sessions
cookie、csrf、cors、httpx、redact、log、metrics
discovery、scoperegistry、timex、i18n境界は構造的に強制されています。外部コードは internal/ に届きません。組み込み側が制御する差し込み口(オプション、store interface、authenticator、audit subscriber)はすべて op/ 配下にあります。
ハンドラグラフ
op.New は *http.ServeMux を構築し、設定されたパスにハンドラをマウントします(下図はデフォルト):
feature.*(PAR、Introspect、Revoke、DynamicRegistration、BackChannelLogout)で制御されるエンドポイントは、対応する feature が有効になっているか、対応するオプション(WithDynamicRegistration など)が渡されたときだけマウントされます。discovery document も、実際にマウントされたエンドポイントだけを公開します。
クロスカットなミドルウェア
すべてのハンドラは以下にラップされます:
| Layer | ソース | 役割 |
|---|---|---|
| CORS | internal/cors | /token、/userinfo、/revoke、/introspect には厳格な許可リスト。/jwks と discovery は public CORS |
| 信頼プロキシ | internal/httpx | WithTrustedProxies を元に、X-Forwarded-* / Forwarded から実クライアント IP を解決 |
| Cookie | internal/cookie | __Host- プリフィックス、AES-256-GCM、session は SameSite=Lax、互換可能なところは Strict |
| CSRF | internal/csrf | consent / logout の POST に対して double-submit + Origin / Referer チェック |
これらはオプションではありません。組み込み側のオプション設定に関係なく構造的に適用されます。
Authorize → token のライフサイクル
最も流量の多いパスです。概略は次のとおりです:
/par と /end_session も大筋は同じ形です。上記が標準的な happy path です。
LoginFlow の内部
WithLoginFlow(LoginFlow{...}) は構築時に内部のパイプラインへコンパイルされます:
LoginFlow {Primary, Rules[], Decider, Risk}
│
▼ (compile)
internal/authn/CompiledLoginFlow
├── Primary → Authenticator(Step descriptor → runtime 実装の解決)
├── Rules[] → 順序付き (When, Then) ペア
├── Decider → 任意の short-circuit
└── Risk → 評価パスごとに 1 回呼ばれる各 authorize リクエストでは:
Primary.Beginがinteraction.Step(Prompt または Result)を返します。- UI ドライバ(HTML または React)がプロンプトを描画し、ユーザが送信します。
Primary.ContinueがResult(Identityがバインドされている) まで進めます。- オーケストレータが
LoginContextを組み立てます(subject、scope、完了したステップ、リスクスコア、ACR values)。 Deciderが走ります(non-nil の場合)。Pass以外の判定は short-circuit します。- それ以外は
Rulesを順に評価します。最初にマッチしたルールのStep.Kind()がCompletedStepsにまだ含まれていなければ発火します。 - 発火するルールが無くなるまで繰り返し、その後にセッションを発行します。
ExternalStep 経由で自前の factor を差し込む手順は、ユースケース: カスタム authenticator を参照してください。
ストレージの差し込み口
ライブラリは、あなたの users テーブルを直接読み書きしません。op.Store interface(小さなサブストアの和集合)越しに会話します:
| サブストア | 何が入るか | 配置の目安 |
|---|---|---|
Clients | OAuth クライアントレジストリ | 通常は永続 |
Users | subject + claim | 組み込み側の実装。既存の users テーブルにマッピングすることが多い |
AuthorizationCodes | one-shot な code(PKCE challenge、scope) | 永続 |
RefreshTokens | refresh chain、ローテーション履歴 | 永続 |
AccessTokens | JWT id 側 / opaque token | 永続 |
OpaqueAccessTokens | opaque AT lookup | 永続 |
Grants | (user, client) ごとの consent scope | 永続 |
GrantRevocations | 失効した grant の tombstone | 永続 |
Sessions | ブラウザセッションのレコード | 揮発に置いてもよい |
Interactions | 試行ごとの interaction 状態 | 揮発に置いてもよい |
ConsumedJTIs | JAR / DPoP jti の replay set | 揮発に置いてもよい |
PARs | pushed authorization request | 揮発に置いてもよい |
IATs / RATs | DCR の Initial / Registration Access Token | 永続 |
EmailOTPs、TOTPs、Passkeys、Recovery | ユーザごとの MFA factor レコード | 永続 |
「揮発に置いてもよい」サブストアは composite アダプタ越しに Redis 層へ配置できます。composite は構築時に「永続バックエンドは 1 つ」を強制するので、トランザクショナルクラスタが 2 つのストアにまたがって分裂することはありません。
詳細は hot/cold ストレージ を参照してください。
Discovery document の組み立て
/.well-known/openid-configuration は OP の実効設定から discovery document を組み立てます。アドバタイズされるフィールドはそのまま OP の実挙動を表します。discovery と挙動の間に乖離はありません。理由は以下のとおりです:
response_types_supportedはWithGrants+ FAPI プロファイルから計算されます。token_endpoint_auth_methods_supportedは、WithProfile(profile.FAPI2Baseline)またはFAPI2MessageSigningが有効なときに FAPI の許可リストと交差します。scopes_supportedは組み込みの scope とWithScopeで登録された scope の和集合です。code_challenge_methods_supportedは常に["S256"]です。plainは構造的に存在しません。request_object_signing_alg_values_supportedは JOSE の許可リスト(RS256、PS256、ES256、EdDSA)です。dpop_signing_alg_values_supportedはそれより狭い集合 (ES256、EdDSA、PS256)です。理由は FAQ § DPoP discovery を参照。
次に読む
- Options 索引 — すべての
op.With*を 1 ページに。上のハンドラグラフへのクロスリンク付き。 - Audit イベントカタログ — 各ハンドラ、各段階で何が発火するか。
- カスタム authenticator — オーケストレータのパイプラインがどこで自前コードを呼ぶか。
- hot / cold ストレージ — サブストアの tier 分けと、揮発 / 永続の境界の関係。