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 を構築し、設定されたパスにハンドラを登録します(下図は既定):

op.New が返す http.Handler は、ServeMux が各リクエストパスを対応する内部エンドポイントハンドラへ振り分けます。op.New→ http.HandlerServeMux/.well-known/openid-configurationdiscovery/jwksinternal/jwks/authorizeinternal/authorizeendpoint/parinternal/parendpoint/tokeninternal/tokenendpoint/userinfointernal/userinfo/revokeinternal/revokeendpoint/introspectinternal/introspectendpoint/end_sessioninternal/endsession/registerinternal/registrationendpoint/interaction/…HTML または SPA UI ドライバ

feature.*PARIntrospectRevokeDynamicRegistration)で制御されるエンドポイントは、対応する feature が有効になっているか、対応するオプション(WithDynamicRegistration など)が渡されたときだけ公開されます。バックチャネルログアウトは feature-gated エンドポイントではなく、/end_session から発火する送信専用の一斉通知です(各 RP に登録された backchannel_logout_uri へ logout token を POST します)。discovery 文書も、実際に公開されたエンドポイントだけを広告します。

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

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

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 チェック

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

Authorize → token のライフサイクル

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

authorize から token までの正常系: ブラウザが /authorize と interaction を進め、RP が /token で code を引き換えます。RPユーザエージェントOPStoreLoginFlow1リダイレクト → /authorize?…2GET /authorize3Clients.GetClient / redirect_uri 検証4PKCE / scope / response_type チェック5302 → /interaction/{uid}6POST /interaction/{uid} (ログイン)7Begin / Continue (Step chain)8Result (subject + AAL + AMR)9200 同意画面 → /interaction/{uid}10POST /interaction/{uid} (同意)11AuthorizationCodes.Save (code + PKCE)12302 → redirect_uri?code=…&state=…&iss=…13code 付きで到達14POST /token (grant_type=authorization_code)15AuthorizationCodes.Consume / PKCE 検証 / クライアント認証16AccessTokens.Register / RefreshTokens.Save17200 { access_token, id_token, refresh_token? }

/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     → 評価パスごとに 1 回呼ばれる
WithLoginFlow は Primary / Rules / Decider / Risk の指定を CompiledLoginFlow にコンパイルし、オーケストレータがリクエストごとのループで実行します。LoginFlowPrimaryRules[]DeciderRiskコンパイルCompiledLoginFlowinternal/authnprimaryrulesdeciderriskauthorize リクエストごとPrimary.Begin /Continue → Step画面表示 → ユーザ送信Result が Identity を確定LoginContextDeciderその後 Rules 評価ルールが尽きるまで繰り返し発火なしセッション発行

各 authorize リクエストでは:

  1. Primary.Begininteraction.Step(Prompt または Result)を返します。
  2. UI ドライバ(HTML または SPA)が画面を描画し、ユーザが送信します。
  3. Primary.ContinueResultIdentity が確定している)まで進めます。
  4. オーケストレータが LoginContext を組み立てます(subject、scope、完了したステップ、リスクスコア、ACR values)。
  5. Decider が動きます(nil 以外の場合)。Pass 以外の判定はそこで短絡します。
  6. それ以外は Rules を順に評価します。最初にマッチしたルールの Step.Kind()CompletedSteps にまだ含まれていなければ発火します。
  7. 発火するルールが無くなるまで繰り返し、その後にセッションを発行します。

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 は構築時に「永続バックエンドは 1 つ」を強制するので、トランザクション対象のサブストアが 2 つのストアに分裂することはありません。

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 プロファイルから計算されます。
  • 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 を参照。

次に読む