Skip to content

ユースケース — FAPI 2.0 Baseline

FAPI 2.0 とは

FAPI("Financial-grade API")は OpenID Foundation が策定する OAuth 2.0 + OIDC のプロファイルです。基盤となる仕様の 厳しいサブセット を選び、攻撃者に長年悪用されてきた任意性を禁じます — 例えば RS256 を弾いて ES256 / PS256 を必須化、PKCE をすべての認可で必須化、送信者制約付きトークン(DPoP または mTLS)を必須化、RP の /authorize 要求を生のクエリ文字列ではなく PAR + JAR で送らせる、などです。

このバーが要求される理由は、銀行・医療・行政の運用が「フラグを全部覚えていますか?」ではなく「チェックリストに対して監査可能か」を要求するからです。FAPI 2.0 は FAPI 1.0(こちらも依然現役)の後継。FAPI 2.0 Baseline はエントリーレベル、FAPI 2.0 Message Signing が JARM + DPoP nonce + RS 側の proof 署名を追加する上位層です。

本ライブラリは Baseline を プロファイル 1 つの切り替えop.WithProfile(profile.FAPI2Baseline))として公開し、必須フラグをまとめて立て、プロファイルを暗黙に違反するような構成では op.New 自体が起動を拒否します。

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

このページで触れる仕様

ソース: examples/03-fapi2/main.go

FAPI 2.0 Baseline が要求するもの

要件RFCライブラリの挙動
Pushed Authorization RequestsRFC 9126プロファイルが feature.PAR を自動有効化。/parrequest_uri が唯一の authorize 入口。
Proof Key for Code ExchangeRFC 7636code_challenge_method=S256 必須、plain 拒否。
送信者制約付きトークン(DPoP または mTLS)RFC 9449 / RFC 8705プロファイルが RequiredAnyOf=[DPoP, MTLS] を立て、どちらかが有効でなければ起動を拒否。
ES256(または PS256)署名RFC 7518アルゴリズム allow-list が FAPI 公開面から RS256 を除外、none/HS* はそもそも存在しない。
redirect_uri 完全一致FAPI 2.0 §5.3ワイルドカード無し、バイト一致比較。
private_key_jwt または mTLS クライアント認証FAPI 2.0 §3.1.3token endpoint auth-method リストを FAPI allow-list と交差。

アーキテクチャ

コード(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"
)

provider, err := op.New(
  op.WithIssuer(demoIssuer),
  op.WithStore(inmem.New()),
  op.WithKeyset(opKeys.Keyset()),
  op.WithCookieKey(opKeys.CookieKey),
  op.WithProfile(profile.FAPI2Baseline), // <--- プロファイル切り替え
  op.WithFeature(feature.DPoP),          // sender-constraint binding を選択
  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"},
  }),
)

PrivateKeyJWTClient は FAPI クライアント用の型付き seed で、token_endpoint_auth_method=private_key_jwt を自動でセットします。組み込み側でこのフィールドを書く必要はありません。同種の typed seed として op.PublicClientop.ConfidentialClient があり、3 つすべてが op.ClientSeed を実装し、WithStaticClients(seeds ...ClientSeed) に渡せます。

WithProfile 呼び出しは:

  1. feature.PARfeature.JAR を自動有効化(sender-constraint の binding として feature.DPoPfeature.MTLS を選ぶのは組み込み側の責務で、WithFeature で明示する)。
  2. token_endpoint_auth_methods_supported を FAPI 2.0 §3.1.3 allow-list(private_key_jwttls_client_authself_signed_tls_client_auth)と交差。
  3. ID Token 署名 alg を ES256/PS256 にロック、新規発行で RS256 を拒否。
  4. redirect_uri の完全一致を強制(どこにもワイルドカード無し)。

DPoP の代わりに mTLS

同じプロファイルは RequiredAnyOf=[DPoP, MTLS] のまま — DPoP の代わりに(または併用で)feature.MTLS を有効にし、TLS 終端 proxy 用に op.WithMTLSProxy(...) を設定してください。FAPI グレードの TLS ヘルパーは examples/50-fapi-tls-jwks

サーフェス確認

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
}'

期待値:

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", "PS256"]
}

適合状況

OFCS の fapi2-security-profile-id2-test-plan はこの実装を検査します。最新 baseline で 48 PASSED / 9 REVIEW(手動レビュー)/ 1 SKIPPED(追加のクライアント鍵が必要な RSA 鍵での負例)/ 0 FAILED

OFCS 全体像と REVIEW / SKIPPED 内訳は OFCS 適合状況 を参照。