Skip to content

アーキテクチャ概観

op.New(...)http.ServeMux を内部に持つ http.Handler を返します。本ページでは、リクエスト到着からレスポンスまでの間に OP が何を実行するか、関わるパッケージ、検証の順序、組み込み側が制御するストレージの差し込み口を整理します。

パッケージ構成

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
  authorizeendpoint、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 を構築し、設定されたパスごとにハンドラを 1 つ登録します。既定のパスと、その裏にあるパッケージは次のとおりです。

パスハンドラのパッケージ何を返すか公開条件
/.well-known/openid-configurationinternal/discoveryOP の実効設定を discovery 文書として返す常時
/jwksinternal/jwks署名鍵セットの公開側(設定時は use=enc 鍵も)常時
/authinternal/authorizeendpointリクエストの解析、クライアントと redirect_uri の検証、interaction へのリダイレクトauthorization_code grant が有効な場合
/interaction/{uid}internal/authorizeendpoint + op/interaction ドライバログインと同意の ceremony。HTML で描画するか、SPA へ JSON で返す/auth と同時
/parinternal/parendpointpushed authorization request を受け取り、一度限りの request_uri を返すfeature.PAR
/tokeninternal/tokenendpointクライアント認証、認可コードとリフレッシュトークンの引き換え、トークン発行常時
/userinfointernal/userinfo提示されたアクセストークンが指す subject の claim常時
/revokeinternal/revokeendpointRFC 7009 によるリフレッシュトークン / アクセストークンの失効feature.Revoke
/introspectinternal/introspectendpointRFC 7662 によるトークン状態を、認可済みのリソースサーバへ返すfeature.Introspect
/end_sessioninternal/endsessionRP 起点のログアウト。バックチャネルの一斉通知もここからsession manager を設定したとき
/registerinternal/registrationendpoint動的クライアント登録と、クライアントごとの管理ルートWithDynamicRegistration

バックチャネルログアウトに行がないのは、公開されるエンドポイントではないからです。/end_session から発火する送信専用の一斉通知であり、OP が各 RP の登録済み backchannel_logout_uri へ logout token を POST します。discovery 文書は実際に公開されたエンドポイントだけを広告するので、feature.Introspect を有効にしていない構成では /introspect を一切告知しません。

クロスカットなミドルウェア

すべてのハンドラは以下にラップされます:

Layerソース役割
CORSinternal/corsdiscovery と /jwks は public CORS。/userinfo/token、interaction / session の JSON 面、公開済みのプロトコルエンドポイント(/par/revoke/introspect/register/bc-authorize/device_authorization/end_session など)は厳格な許可リスト
信頼プロキシinternal/httpxWithTrustedProxies を元に、X-Forwarded-* / Forwarded から実クライアント IP を解決
Cookieinternal/cookie__Host- プリフィックス、AES-256-GCM、session は SameSite=Lax、互換可能なところは Strict
CSRFinternal/csrfconsent / logout の POST に対して double-submit + Origin / Referer チェック

これらはオプションではありません。組み込み側のオプション設定に関係なく構造的に適用されます。

origin の集合は意図的に分かれています。API の CORS は明示的な WithCORSOrigins でのみ拡張され(API 互換性のため redirect URI の origin も含まれます)、interaction と end-session の ceremony 検査は issuer origin と明示的な origin だけを許可します。redirect URI の origin だけで cross-origin interaction / logout POST が許可されることはありません。

Authorize → token のライフサイクル

最も流量の多いパスです。概略は次のとおりです。

authorize から token まで — ストアと LoginFlow を含めて
authorize リクエストからトークン応答までの正常系を、どの呼び出しがストアに届き、どの呼び出しが LoginFlow に届くかまで含めて示した図。最初の 2 段階はブラウザが進め、3 段階目は RP と OP の直接のバックチャネル通信です。1 · authorize リクエスト2 · interaction3 · 認可コードの引き換えRP組み込み側のアプリブラウザuser agentOPgo-oidc-providerストア組み込み側の実装LoginFlow組み込み側の実装1/authorize?… へリダイレクト2GET /authorize3Clients.GetClient · redirect_uri の完全一致4PKCE · scope · response_type の検査5302 → /interaction/{uid}6POST /interaction/{uid} — ログイン7Begin / Continue — Step チェーン8Result — subject · AAL · AMR9200 同意画面10POST /interaction/{uid} — 同意11AuthorizationCodes.Save — code + PKCE12302 → redirect_uri?code&state&iss13code を持って到達する14POST /tokengrant_type=authorization_code15AuthorizationCodes.Consume続けて PKCE とクライアント認証を検証する16AccessTokens.Register · RefreshTokens.Save17200 · { access_token, id_token, refresh_token? }
右端の 2 レーンは組み込み側のものです。そこへ向かう矢印はすべてインタフェース呼び出しで、だからこそ中央のプロトコル処理に手を入れずに、保存先とログイン体験を差し替えられます。

/par/end_session も大筋は同じ形です。上記が標準的な成功経路です。

LoginFlow の内部

WithLoginFlow(LoginFlow{...}) は構築時に内部のパイプラインへコンパイルされます:

LoginFlow {Primary, Rules[], Decider, Risk}

    ▼ (compile)
internal/authn/CompiledLoginFlow
    ├── Primary  → Authenticator(Step descriptor → runtime 実装の解決)
    ├── Rules[]  → 順序付き (When, Then) ペア
    ├── Decider  → 任意の short-circuit
    └── Risk     → chain 開始時に 1 回、score を cache
LoginFlow — 宣言し、一度コンパイルし、リクエストごとに回す
WithLoginFlow に渡すのは仕様です。これは一度 CompiledLoginFlow にコンパイルされ、オーケストレータがリクエストごとにループで実行します。第一要素を開始し、画面を出し、Identity を確定し、Decider と Rules を評価し、発火するルールが無くなるまで繰り返します。宣言一度だけコンパイルリクエストごとに実行LoginFlowWithLoginFlow に渡すものPrimary第一要素Rules[]追加要素を求める条件Decider · RiskAAL のポリシーとシグナルコンパイルCompiledLoginFlowinternal/authnprimaryrulesdeciderriskop.New の時点で解決・検証されるPrimary.Begin → Stepチェーン開始時に 1 回画面表示 → ユーザが送信Step は 1 つずつResult が Identity を確定するsubject · AAL · AMRLoginContext → Decider、続けて Rulesルールが発火 → 次の Step へ発火するルールが無いセッションを発行する
先にコンパイルしておくことで、設定の誤りが実行時ではなく起動時のエラーになります。リクエストが届く頃には解釈すべきものは残っておらず、あとはチェーンを辿るだけです。

各 authorize リクエストでは:

  1. Primary.Begininteraction.Step(Prompt または Result)を返します。
  2. UI ドライバ(HTML または SPA)が画面を描画し、ユーザが送信します。
  3. Primary.ContinueResultIdentity が確定している)まで進めます。
  4. オーケストレータが LoginContext を組み立てます(subject、scope、完了したステップ、cache 済みのリスクスコア、ACR values)。
  5. Decider が動きます(nil 以外の場合)。Pass 以外の判定はそこで短絡します。
  6. それ以外は Rules を順に評価します。最初にマッチしたルールの Step.Kind()CompletedSteps にまだ含まれていなければ発火します。
  7. 発火するルールが無くなるまで繰り返し、その後にセッションを発行します。LoginFlow.Risk は chain ごとに最大 1 回だけ呼ばれ、結果は cache されます。provider-level の WithRiskAssessor は別の seam であり、LoginFlow.Risk と同時に指定すると、どちらかを黙って選ばず構築時に拒否されます。

ExternalStep 経由で自前の factor を差し込む手順は、使い方: カスタム authenticator を参照してください。

ストレージの差し込み口

ライブラリは、組み込み側の users テーブルを直接読み書きしません。store.Store interface(小さなサブストアの和集合)越しに会話します:

サブストア何が入るか置き場所の目安
ClientsOAuth クライアントレジストリ通常は永続
Userssubject + claim組み込み側の実装。既存の users テーブルにマッピングすることが多い
AuthorizationCodesone-shot な code(PKCE challenge、scope)永続
RefreshTokensリフレッシュトークンの連鎖、ローテーション履歴永続
AccessTokensJWT id 側 / opaque token永続
OpaqueAccessTokensopaque AT lookup永続
Grants(user, client) ごとの consent scope永続
GrantRevocations失効した grant の tombstone永続
Sessionsブラウザセッションのレコード揮発に置いてもよい
Interactions試行ごとの interaction 状態揮発に置いてもよい
ConsumedJTIsJAR / DPoP jti のリプレイ検出集合揮発に置いてもよい
PARspushed authorization requestPAR が authorization-code transaction に参加する構成では永続
IATs / RATsDCR の Initial / Registration Access Token永続
DeviceCodesRFC 8628 のデバイス認可レコード永続
CIBARequestsOpenID Connect CIBA の backchannel authentication レコード永続
MetadataOP 内部の key/value 状態(例: subject_mode マーカー)永続(未対応バックエンドは nil 可)

「揮発に置いてもよい」サブストアは composite アダプタ越しに Redis 層へ置けます。composite アダプタは構築時に、すべての Kind に振り分け先があることを要求します。composite.TxClusterKinds の 7 要素は、すべて同じ比較可能なバックエンドに解決され、そのバックエンドが store.Transactional を実装していなければなりません。

  • 認可コード
  • リフレッシュトークン
  • grant
  • PAR
  • JWT アクセストークン登録
  • opaque アクセストークン
  • grant 失効 tombstone

それ以外の Kind は別のバックエンドに配置できます。無効な Kind、未設定の振り分け先、cluster の分割、比較不能な cluster バックエンド、非トランザクショナルなアンカーは、いずれも構築エラーになります。

MFA factor のストア(EmailOTPStoreTOTPStorePasskeyStoreRecoveryStoreAuthnLockoutStore)は store.Store のサブストアではありません。LoginFlow を組み立てる際に、対応する login-flow 値(StepEmailOTP.StoreStepTOTP.StorePrimaryPasskey.StoreStepRecoveryCode.StoreWithAuthnLockoutStore)へ直接渡します。in-memory、SQL、DynamoDB adapter は同じ名前の accessor でこれらを公開します。examples/27-durable-mfa-store は、同梱 SQL factor store と OP のコアテーブルを 1 つの DB で使う例です。自前バックエンドでは同じ契約を実装します。

詳細は hot/cold ストレージ を参照してください。

Discovery 文書の組み立て

/.well-known/openid-configuration は OP の実効設定から discovery 文書を組み立てます。広告されるフィールドはそのまま OP の実挙動を表します。discovery と挙動の間に乖離はありません。理由は以下のとおりです。

  • response_types_supportedWithGrants + FAPI プロファイルから計算されます。client-credentials だけの OP では空配列となり、browser authorization / logout の surface と flag は広告されません。
  • token_endpoint_auth_methods_supported は、WithProfile(profile.FAPI2Baseline) または FAPI2MessageSigning が有効なときに FAPI の許可リストと交差します。
  • scopes_supported は組み込みの scope と WithScope で登録された scope の和集合です。
  • ui_locales_supported は runtime locale resolver(seed bundle + WithLocale 追加分)から自動導出されます。WithDiscoveryMetadata(...).UILocalesSupported に非空の明示リストを渡した場合だけ、それが優先されます。
  • code_challenge_methods_supported は常に ["S256"] です。plain は構造的に存在しません。
  • request_object_signing_alg_values_supported は JOSE の許可リスト(RS256PS256ES256EdDSA)です。
  • dpop_signing_alg_values_supported はそれより狭い集合(ES256EdDSAPS256)です。理由は FAQ § DPoP discovery を参照。

次に読む