ユースケース — FAPI 2.0 Message Signing
profile.FAPI2MessageSigning は、FAPI 2.0 Baseline に 3 つの署名付きメッセージ(認可リクエスト、認可レスポンス、introspection レスポンス)を加えたものです。Baseline のページに挙げた内容はすべてそのまま当てはまります。PAR、PKCE、DPoP または mTLS、private_key_jwt または証明書によるクライアント認証、クライアント assertion の aud を issuer だけにすること、ローテーションしないリフレッシュトークンです。このページが扱うのは、そこから変わる点だけです。
op.New(
/* FAPI2Baseline と同じオプション */
op.WithProfile(profile.FAPI2MessageSigning),
op.WithDPoPNonceSource(nonces), // DPoP が有効な間は必須。下記を参照
)このページで触れる仕様
- FAPI 2.0 Message Signing — 最終版
- RFC 9101 — JWT-Secured Authorization Request (JAR)
- JARM — JWT-Secured Authorization Response Mode
- RFC 9701 — JWT Response for OAuth Token Introspection
- RFC 9449 — DPoP
プロファイルが入れるもの、利用者が用意するもの
Message Signing は、Baseline の表の内容をすべて含み、次の行が加わります。
| プロファイルが入れるもの | 利用者が用意するもの |
|---|---|
必須の JAR: /par と /auth へのリクエストはすべて署名付き request object を伴う | すべてのクライアントの鍵セット(JWKS または JWKSURI)と、PS256・ES256・EdDSA で署名するクライアント |
JARM: /auth のレスポンスは署名付き JWT | OP の JWKS で JARM レスポンスを検証できるクライアント |
署名付き introspection: /introspect は RFC 9701 の JWT で応答 | リソースサーバが呼ぶ場合は feature.Introspect、署名付き応答を検証するリソースサーバ(API を保護する) |
| DPoP が有効な間は DPoP nonce のチャレンジ | nonce source: レプリカ 1 台なら op.NewInMemoryDPoPNonceSource、複数なら op/storeadapter/redis の Store.DPoPNonceSource など共有できるもの |
Baseline の表もこの表も、すべてのレプリカで共有するストアと、プロキシの背後では op.WithTrustedProxies(mTLS ならさらに op.WithMTLSProxy と op.WithMTLSRootCAs)を前提にしています。動かせる版は examples/54-fapi2-message-signing です。
チェックリスト
署名付き request object が必須になる
/par と /auth へのリクエストは、すべて署名付きの JAR request object を伴う必要があります。Baseline は request object を受け付けつつ通常のパラメータも受け付けますが、Message Signing では署名付きオブジェクトのないリクエストを拒否します。プロファイルが feature.JAR を有効にするので、必要な作業はクライアント側とクライアント登録だけです。
- OP が request object を検証できる鍵セット(
JWKSまたはJWKSURI)を、クライアントに登録します。private_key_jwtのクライアントなら、すでに持っています。 - クライアントの署名は
PS256、ES256、EdDSAのいずれかで、RS256は拒否されます。 - request object は署名されていない query / form パラメータとマージされず、置き換えます。認可パラメータはすべて署名付きオブジェクトの中に入れてください。
- オブジェクトの
iatは最大 60 分前まで許容されます(profile.MaxRequestObjectAge)。nbfとexpの検証も引き続き行われます。
認可レスポンスは JARM になる
/auth のレスポンスは、すべて OP の有効な署名鍵で署名した JARM の JWT で包まれます。プロファイルが feature.JARM を有効にするので、ほかに設定するものはありません。RP は OP の JWKS で JWT を検証し、その claim から code、state、iss を読み取ります。JARM のレスポンスを処理できないクライアントは、このプロファイルを使えません。
introspection レスポンスが署名される
/introspect をマウントしている場合、成功レスポンスはすべて RFC 9701 の JWT 形式になり、呼び出し側が送った Accept ヘッダやクライアントのメタデータにかかわらず、OP の有効な鍵で署名されます。プロファイル自体はこのエンドポイントをマウントしません。リソースサーバが呼び出すなら feature.Introspect を有効にし、呼び出し側が署名付き JWT のレスポンスを受け付けて JWKS で検証できることを確認してください。
DPoP が有効な間は DPoP nonce source が必須になる
DPoP が有効で nonce source が未設定だと、op.New は WithProfile(profile.FAPI2MessageSigning) requires a DPoP nonce source because DPoP is active というエラーで失敗します。DPoP が有効になるのは、feature.DPoP を指定した場合と、feature.DPoP も feature.MTLS も指定しなかったためにプロファイルが DPoP を自動で選んだ場合です。送信者制約に feature.MTLS を選んだ OP は、nonce source を必要としません。
この要件は本ライブラリのプリセットによるものです。最終版の FAPI 2.0 Security Profile と Message Signing 仕様では DPoP nonce は任意なので、適合の主張はこの要件に左右されません。source を設定すると、/token、/par、/userinfo は、受理できる nonce を含まない proof に use_dpop_nonce と新しい DPoP-Nonce ヘッダで応答します(DPoP nonce フロー)。profile.FAPICIBA も同じ要件を持ちます。
複数レプリカでは nonce source を共有する
op.NewInMemoryDPoPNonceSource はプロセス内で閉じているため、あるレプリカが発行した nonce は別のレプリカでは通用しません。op/storeadapter/redis の Redis source を使ってください。
nonces, err := redisStore.DPoPNonceSource(time.Minute)
if err != nil {
log.Fatal(err)
}
op.WithDPoPNonceSource(nonces)store のキー prefix を共有するレプリカは、nonce の状態も共有します。障害時の挙動は 複数インスタンス運用 にあります。
変わらないもの
クライアント認証の選択肢、aud の規則、ローテーションしないリフレッシュトークン、アクセストークン 10 分の上限、WithPARTTL の上限、redirect_uri の完全一致、TLS まわりの判断(プロキシの扱い、op.FAPITLSConfig に必要な RSA 証明書)は Baseline と同じです。これらは Baseline のページを参照してください。
次に読む
- FAPI 2.0 Baseline — このページの土台になるプロファイル。
- DPoP nonce フロー — nonce のやり取りと組み込み手順。
- FAPI 2.0 入門 — 署名付きメッセージそれぞれの目的。
- JARM — 署名付き認可レスポンスの組み立てと検証。