Skip to content

ユースケース — FAPI 2.0 Baseline ​

プロファイルが入れるもの、利用者が用意するもの ​

op.WithProfile(profile.FAPI2Baseline) はプロトコル上の設定を固定します。動く構成にするための残りは、利用者の側で用意します。

プロファイルが入れるもの利用者が用意するもの
PAR、および S256 だけの PKCEクライアント登録: 公開 JWKS を付けた op.PrivateKeyJWTClient。feature.MTLS を使うなら op.TLSClientAuthClient または op.SelfSignedTLSClientAuthClient
DPoP に結び付いたトークン。feature.MTLS を有効にしない限り自動で有効リスナーの TLS: op.FAPITLSConfig()。TLS 1.2 のために RSA のサーバ証明書が必要
クライアント認証は private_key_jwt に限定(feature.MTLS があれば 2 つの mTLS 方式も)プロキシの背後では op.WithTrustedProxies。mTLS ならさらに op.WithMTLSProxy と op.WithMTLSRootCAs
クライアント assertion の aud は issuer の文字列だけを受理すべてのレプリカで共有するストア(複数レプリカ)
ローテーションしないリフレッシュトークントークンを検証するリソースサーバ: API を保護する
アクセストークンの有効期間は最大 10 分、PAR の有効期間は 600 秒未満
クライアント署名の入力は PS256・ES256・EdDSA、ID トークンは ES256

動かせる版: TLS と JWKS の組み立ては examples/50-fapi-tls-jwks、プロキシの背後での証明書バウンドトークンは examples/55-mtls-proxy です。

最終版 Security Profile との関係 ​

OpenID Foundation は FAPI 2.0 Security Profile を最終版として公開しました。以前のドラフトでは同じプロファイルが「Baseline」と呼ばれており、本ライブラリはそのプリセットの名前として profile.FAPI2Baseline を残しています。プリセットに反する構成は op.New が拒否するので、追加のオプションなしで、最終版の次の要件が満たされます。

  • PAR と PKCE。 認可リクエストは /par 経由で届き、code_challenge_method=S256 だけが受け付けられます。
  • 送信者制約付きトークン。 DPoP または mTLS です。どちらも指定されていなければ、プリセットが DPoP を有効にします。
  • クライアント認証。 private_key_jwt、または feature.MTLS が有効なら RFC 8705 §2 の 2 つの mTLS 方式です。共有シークレット方式と公開クライアントは拒否されます。
  • クライアント assertion の aud(§5.3.2.1(8))。issuer 識別子を単一の JSON 文字列で指定する必要があります。配列やエンドポイント URL は invalid_client で拒否されます。他のプロファイルは、これまでどおり issuer と token endpoint URL のどちらも受け付けます。
  • リフレッシュトークンのローテーションなし(§5.3.2.1(9))。リフレッシュ応答は新しいアクセストークンを返し、refresh_token を含みません。クライアントが持つリフレッシュトークンは、期限が切れるか失効するまで使えます。ローテーションによる再利用検知はこのプリセットにはありませんが、失効、grant の失効、ログアウトによる無効化は従来どおり働きます。op.WithRefreshGracePeriod が設定する窓はないため、op.New が拒否します。
  • DPoP の htu。 OP 自身のエンドポイントでは、proof の htu を OP が discovery で広告するエンドポイント URL と比較します。mtls_endpoint_aliases を設定していれば、その URL とも比較します。
  • アルゴリズムと有効期間(§5.4.1、§5.3.2.2)。クライアントが署名する入力は PS256、ES256、EdDSA、PAR の request_uri の有効期間は 600 秒未満、アクセストークンはサーバ側で失効できること、です。

最終版を超える部分として、プリセットは JAR のサポートを有効にし、事前登録済みの redirect_uri の完全一致を求め、ID トークンを ES256 だけで署名し、RS256 での発行を拒否し、アクセストークンを 10 分までに制限します。これらは本ライブラリの選択で、最終版の要件ではありません。op.New が通ったことは適合の主張にはなりません。TLS の設定、クライアントの挙動、認定そのものはプリセットの範囲外です。

FAPI 2.0 とは ​

FAPI("Financial-grade API")は OpenID Foundation が策定する OAuth 2.0 のセキュリティプロファイル群です。FAPI 2.0 Security Profile は、PAR、S256 の PKCE、送信者制約付きトークン(DPoP または mTLS)、confidential クライアント、PS256 / ES256 / EdDSA の JWT alg を要求します。JAR は要件に含まれません。また OAuth のプロファイルなので、認証済みユーザの識別子が必要なときに OIDC を併用します。認可エンドポイントは mount prefix 配下の /auth で、既定の /oidc なら /oidc/auth になります。

銀行・医療・行政の運用では、「安全なオプションを全部覚えているか」ではなく「決まったチェックリストに対して監査できるか」が問われます。FAPI 2.0 は、いまも使われている FAPI 1.0 の後継です。別の設定 profile.FAPI2MessageSigning は、JARM、署名付き request object、署名付き introspection を追加します。組み込み側で何が変わるかは FAPI 2.0 Message Signing にまとめています。

本ライブラリは Baseline を プロファイル 1 つの指定(op.WithProfile(profile.FAPI2Baseline))として公開します。必要なチェックをまとめて有効にし、それに反する構成では op.New 自体が起動を拒否します。

PAR / JAR / JARM / DPoP / mTLS / ES256 など各略号の解説は FAPI 2.0 入門 にあります。本ページは構成例を扱います。

このページで触れる仕様

ソース: examples/03-fapi2/main.go はプロファイルのフローを扱います。examples/50-fapi-tls-jwks は op.FAPITLSConfig() と op.LoadPublicJWKS を示します。LoadPublicJWKS は、client 登録前に private JWK material を取り除きます。

本ライブラリの FAPI2Baseline が強制するもの ​

要件RFCライブラリの挙動
Pushed Authorization RequestsRFC 9126プロファイルが feature.PAR を自動有効化。/par で得た request_uri を authorize の入口にする。
Proof Key for Code ExchangeRFC 7636code_challenge_method=S256 必須、plain 拒否。
送信者制約付きトークン(DPoP または mTLS)RFC 9449 / RFC 8705このプロファイルでは profile.RequiredAnyOf が {DPoP, MTLS} を返すため、どちらか一方を満たせばよい。どちらも指定されていなければ、コンストラクタがインフラ前提の少ない DPoP を有効にする。
JWT algFAPI 2.0 §5.4.1プロファイルが許可するのは PS256、ES256、EdDSA です。本ライブラリの id_token_signing_alg_values_supported はプロファイル下で ["ES256"] です。op.New は RS256 との併用を拒否し、none / HS* は公告しません。
クライアント認証FAPI 2.0 §5.3.2.1private_key_jwt。feature.MTLS を有効にすれば tls_client_auth と self_signed_tls_client_auth も使える。クライアント認証 を参照。
クライアント assertion の audFAPI 2.0 §5.3.2.1(8)issuer 識別子を JSON 文字列で指定したものだけ。配列やエンドポイント URL は invalid_client。
リフレッシュトークンFAPI 2.0 §5.3.2.1(9)ローテーションしない。リフレッシュ応答に refresh_token は含まれず、op.WithRefreshGracePeriod は op.New が拒否する。
DPoP の htuRFC 9449 §4.3OP が広告するエンドポイント URL、または mtls_endpoint_aliases の URL と比較する。
短命なアクセストークンライブラリのポリシーアクセストークンの有効期間が 10 分を超える設定は op.New が拒否する(profile.MaxAccessTokenTTL)。
PAR の有効期間FAPI 2.0 §5.3.2.2op.WithPARTTL が 600 秒以上なら op.New が拒否する。
署名付き request objectRFC 9101feature.JAR が自動で有効になり、/par と /auth は request を受け付ける。ただし必須ではない(必須にするのは profile.FAPI2MessageSigning)。request object を使う場合は、署名されていない query / form パラメータとマージせず、request object が置き換える。request object とクライアントアサーションの RS256 は拒否する。
redirect_uri 完全一致ライブラリのポリシー本ライブラリは事前登録済み URI とのバイト単位の一致を求め、ワイルドカードを認めません。現行の FAPI 2.0 Security Profile は、認証済み PAR リクエストに redirect_uri を含めることを求めますが、事前登録は必須としていません。

アーキテクチャ ​

本ライブラリの FAPI2Baseline の一連の流れ
本ライブラリの FAPI2Baseline では、RP が認可リクエストを pushed authorization エンドポイントへ送り、request_uri を受け取ります。ブラウザが運ぶのはその参照だけです。トークンリクエストは private_key_jwt で認証され DPoP proof を伴い、アクセストークンは DPoP にバインドされ、ID Token は本ライブラリの設定により ES256 で署名されます。RP / クライアントprivate_key_jwtOPgo-oidc-provider1POST /parclient_assertion · code_challenge=S2562201 · request_uri=urn:…:<id> · expires_inGET /auth?request_uri=urn:…&client_idプロファイルに照らして検証するredirect_uri exact matchライブラリの ID Token: ES256interaction エンドポイントが認可判断を得る302 redirect_uri?code=…&state=…POST /token · DPoP: <proof>code · code_verifier · client_assertion200access_token (DPoP バインド) · id_token · refresh_token
本ライブラリのプロファイルが前段に足しているのは手順 1 と 2 です。これ以降ブラウザが運ぶのはリクエスト本体ではなく参照になるため、URL バーに改竄する価値のあるものが残りません。

コード(examples/03-fapi2 からの抜粋) ​

go
import (
  "github.com/libraz/go-oidc-provider/op"
  "github.com/libraz/go-oidc-provider/op/feature"
  "github.com/libraz/go-oidc-provider/op/profile"
  "github.com/libraz/go-oidc-provider/op/storeadapter/inmem"
)

const (
  demoIssuer      = "https://op.example.com"
  demoClientID    = "fapi2-example-client"
  demoRedirectURI = "https://rp.example.com/callback"
)

st := inmem.New()
loginFlow := op.LoginFlow{Primary: op.PrimaryPassword{Store: st.UserPasswords()}}

provider, err := op.New(
  op.WithIssuer(demoIssuer),
  op.WithStore(st),
  op.WithKeyset(opKeys.Keyset()),
  op.WithCookieKeys(opKeys.CookieKey),
  op.WithLoginFlow(loginFlow),
  op.WithProfile(profile.FAPI2Baseline), // <--- プロファイル切り替え
  op.WithFeature(feature.DPoP), // 省略可: プロファイルが DPoP をすでに有効にします
  op.WithStaticClients(op.PrivateKeyJWTClient{
    ID:            demoClientID,
    JWKS:          clientJWKs, // 公開 JWK Set を JSON バイト列で
    RedirectURIs:  []string{demoRedirectURI},
    Scopes:        []string{"openid", "profile", "email"},
    GrantTypes:    []string{"authorization_code", "refresh_token"},
    ResponseTypes: []string{"code"},
  }),
)

example は簡潔にするため in-memory のユーザストアを使っています。実際の OP では自前の store.UserPasswordStore を渡します(既存ユーザストアの投影)。PrivateKeyJWTClient は FAPI クライアント用の型付きクライアント定義で、token_endpoint_auth_method=private_key_jwt を自動で設定します。組み込み側でこのフィールドを書く必要はありません。同じ系統の型は クライアント認証 に挙げており、すべてが op.ClientSeed を実装し、WithStaticClients(seeds ...ClientSeed) に渡せます。

example の RP(rpkit.NewFAPI2)は、private_key_jwt のアサーション付きで /par にリクエストを push し、/token では DPoP proof を送ります。そのため http://127.0.0.1:9090/ を開くだけで、ブラウザからフロー全体を試せます。既定の grant は authorization_code と refresh_token なので、example は op.WithGrants を呼びません。

本ライブラリで WithProfile を指定すると、OP は次のように振る舞います。

  1. feature.PAR と feature.JAR を自動で有効にします。
  2. token_endpoint_auth_methods_supported を FAPI 2.0 §5.3.2.1 の許可リストと突き合わせて絞り込みます。残るのは private_key_jwt と、feature.MTLS が有効な場合の 2 つの mTLS 方式です。
  3. id_token_signing_alg_values_supported = ["ES256"] を維持します。OP は ES256 の ID Token を署名・広告します。FAPI 2.0 Security Profile §5.4.1 は RS256 を許可しないため、プロファイルは op.WithSigningAlgorithm(op.AlgRS256) を拒否します。プロファイルは PS256 と EdDSA も許可しますが、ライブラリにはその発行オプションがありません。
  4. redirect_uri の完全一致を強制します。ワイルドカードはどこにも許しません。
  5. DPoP または mTLS の送信者制約要件を満たします。feature.MTLS が明示されていればそれを尊重し、どちらも選ばれていなければ feature.DPoP を追加します。
  6. クライアント assertion の aud を issuer の文字列だけに固定し、リフレッシュトークンのローテーションを止め、op.WithRefreshGracePeriod を拒否します。

JAR では profile.MaxRequestObjectAge(profile.FAPI2Baseline) が 60 * time.Minute を返します。op.New はこの値を request-object verifier に渡し、署名済み request object の iat 経過時間に適用します。nbf と exp の検証も引き続き行われます。

クライアント認証 ​

クライアントごとに方式を 1 つ選びます。seed 型が token_endpoint_auth_method を決めるため、認証方式を指定するフィールドはどの型にもありません。

方式seed 型登録する値追加で必要なもの
private_key_jwtop.PrivateKeyJWTClientJWKS(JSON の公開 JWK Set)か JWKSURI のどちらか 1 つプロファイル以外には不要
tls_client_authop.TLSClientAuthClientSubjectDN、SANDNS、SANURI、SANIP、SANEmail のうちちょうど 1 つWithFeature(feature.MTLS)、チェーン検証済みの証明書
self_signed_tls_client_authop.SelfSignedTLSClientAuthClientJWKS か JWKSURI のどちらか 1 つWithFeature(feature.MTLS)
  • JWKSURI は、他のクライアントの jwks_uri と同じ SSRF 対策付きの経路で取得されます。プライベートネットワーク上のホストは op.WithAllowPrivateNetwork(op.OutboundJWKS) で許可し、op.WithJWKSHTTPTransport で transport を指定します。
  • tls_client_auth が受け付けるのは、チェーンを検証済みの証明書だけです。直接 TLS で受ける場合は、サーバが自前の ClientCAs でクライアント証明書を検証している必要があります。証明書をヘッダで前送りするプロキシの後ろでは、OP が op.WithMTLSRootCAs で検証します。tls_client_auth と op.WithMTLSProxy を組み合わせて root pool がなければ、op.New は失敗します。
  • self_signed_tls_client_auth は、証明書の公開鍵をクライアントの鍵セットと照合するだけで、チェーンは辿りません。
  • feature.MTLS なしで op.WithMTLSProxy や op.WithMTLSRootCAs を設定すると、op.New がエラーを返します。
  • op.WithMTLSProxy(op.MTLSProxy{HeaderName, TrustedProxies}) は、ヘッダ名と、そのヘッダを設定してよいプロキシの CIDR を指定します。TrustedProxies が空なら op.WithTrustedProxies の範囲を使います。
  • クライアントの認証に使った証明書は、そのクライアントが受け取るアクセストークンの結び付け先(cnf.x5t#S256)にもなります。

証明書の扱いの詳細は mTLS にあります。

DPoP の代わりに mTLS

プロファイルの既定の送信者制約方式は、TLS クライアント証明書の設定が不要な DPoP です。mTLS に標準化している配備では feature.MTLS を明示し、TLS 終端プロキシ用に op.WithMTLSProxy(...) を設定してください。その明示選択があれば DPoP 既定は追加されません。送信者制約とクライアント認証は別々の選択です。private_key_jwt で登録したクライアントにも、証明書に結び付いたトークンを発行できます。

FAPI 構成で決めておくこと ​

TLS を終端するプロキシの後ろで動かす ​

DPoP のためにプロキシで特別な設定をする必要はありません。OP は proof の htu を、issuer、mount prefix、エンドポイントのパスから組み立てた広告 URL と比較します。そのため、プロキシがスキームやホストを書き換えても結果は変わりません。op.WithDiscoveryMetadata で mtls_endpoint_aliases を設定した場合の alias URL も同じです。

ただし op.WithTrustedProxies は引き続き必要です。監査記録、リスク判定、lockout が使うクライアントのアドレスと、OP のほかの検査が読むスキームとホストを決めるからです。ここでいう検査は、ログインやログアウトのリクエストが issuer のオリジンから来たかどうかの判定、redirect_uri のスキームの検査、cookie の Secure の判定です。設定しなければ OP は X-Forwarded-* を無視し、警告を 1 回ログに出します。設定方法は ルータへのマウント と issuer にあります。op/rs で作ったリソースサーバは、OP が API の公開 URL を知らないため、DPoP の htu を受け取ったリクエストと比較します。検証器に渡す前に、前送りされたリクエストのホストと TLS の状態を復元してください(API を保護する)。

サーバ証明書 ​

op.FAPITLSConfig() は、TLS 1.2 に RSA 鍵の AEAD スイートだけを許可します。適合性スイートが検査する FAPI 1.0 Read-Write プロファイルの cipher allow-list に従った設定です。そのため RSA のサーバ証明書が必要です。ECDSA 証明書では使える TLS 1.2 スイートが残らず、TLS 1.2 のハンドシェイクは失敗します。TLS 1.3 は allow-list の対象外で、引き続きネゴシエートされます。ECDSA 証明書が必須の環境では、このヘルパーを使わず自前の tls.Config を組み立ててください。プロキシが TLS を終端する場合は、プロキシの cipher ポリシーが適用され、FAPITLSConfig は関与しません。OP の ES256 署名鍵は別の鍵で、影響を受けません。

複数レプリカで動かす ​

レプリカ間では、セッション、grant、replay 用ストアを共有します(複数インスタンス運用)。追加で必要になるのは DPoP nonce source だけで、nonce を有効にする場合に限ります。profile.FAPI2Baseline は nonce を要求しません。op.WithDPoPNonceSource を追加するなら、レプリカ間で共有する必要があります。in-memory の source が発行した値は他のレプリカでは通用しないため、op/storeadapter/redis の Store.DPoPNonceSource を使ってください。profile.FAPI2MessageSigning は、DPoP が有効なとき常にこの source を要求します。

公開面確認 ​

sh
curl -s http://localhost:8080/.well-known/openid-configuration | jq '{
  pushed_authorization_request_endpoint,
  request_parameter_supported,
  dpop_signing_alg_values_supported,
  token_endpoint_auth_methods_supported,
  id_token_signing_alg_values_supported
}'

既定の DPoP による送信者制約での期待値:

json
{
  "pushed_authorization_request_endpoint": "http://localhost:8080/oidc/par",
  "request_parameter_supported": true,
  "dpop_signing_alg_values_supported": ["ES256", "EdDSA", "PS256"],
  "token_endpoint_auth_methods_supported": ["private_key_jwt"],
  "id_token_signing_alg_values_supported": ["ES256"]
}

feature.MTLS を有効にすると、token_endpoint_auth_methods_supported に tls_client_auth と self_signed_tls_client_auth も加わります。profile.FAPI2Baseline 下では id_token_signing_alg_values_supported は ["ES256"] で、OP が発行する ID Token はすべて ES256 署名です。FAPI プロファイルを使わない OP は自身の発行アルゴリズムを公開し、op.WithSigningAlgorithm(op.AlgRS256) の後は ["RS256"] になります。現行の FAPI 2.0 Security Profile は JWT alg として PS256、ES256、EdDSA を許可します(§5.4.1)。本ライブラリの発行アルゴリズムの選択肢(ES256 か RS256)はこれより狭く、RS256 はプロファイル下では拒否されます。dpop_signing_alg_values_supported は DPoP proof 受理用で ["ES256", "EdDSA", "PS256"] です。

適合状況 ​

本ライブラリの profile.FAPI2Baseline はポリシー設定であり、配備が最終版 Security Profile に適合していることを主張するものではありません。最終版 Security Profile との関係 に挙げた差分は、仕様より厳しく設定した部分です。TLS の設定、クライアントの挙動、認定は別の手順になります。

FAPI 2.0 の plan に対する OpenID Foundation 適合性スイートの結果は、スイートのバージョンと実行日とともに OFCS 適合状況 にあります。