はじめに
API がひとつ破られると口座振替が発生する世界があります。オープンバンキング、マイデータ、オープンファイナンスがその世界です。このような環境では「一般的な OAuth 2.0 ベストプラクティス」だけでは不十分だという問題意識から出発したのが OpenID Foundation の FAPI(Financial-grade API)ワーキンググループであり、その成果物が [FAPI 2.0 Security Profile](https://openid.net/specs/fapi-2_0-security-profile.html) です。
2026 年現在、FAPI はもはや金融業界だけの話ではありません。FAPI 2.0 Security Profile と Attacker Model は Final 仕様として確定し、ブラジルのオープンファイナンス、英国のオープンバンキング、オーストラリアの CDR、サウジアラビアのオープンバンキングなど、国家規模のエコシステムが FAPI ベースで運用されています。高い保証レベルが必要なヘルスケアや行政デジタルサービスのようなドメインも FAPI を採用する流れにあります。そして [Keycloak 26.6](https://www.keycloak.org/docs/latest/release_notes/index.html) が FAPI 2.0 Security Profile と Message Signing の Final 版を公式サポートしたことで、オープンソース IdP でも金融グレードのプロファイルを構築できる環境が整いました。
本記事では FAPI 1.0 から 2.0 への進化の背景、中核要件、DPoP と mTLS の選択基準、Keycloak の設定、そして一般的なサービスが FAPI から得られる教訓まで整理します。
FAPI が解決しようとする問題 — 攻撃者モデル
FAPI 2.0 の出発点は [Attacker Model](https://openid.net/specs/fapi-2_0-attacker-model.html) という独立した文書です。一般的な OAuth が暗黙的に仮定するよりもはるかに強力な攻撃者を想定します。
- ネットワーク上の攻撃者:認可リクエスト/レスポンスを読み取り、改ざんできる
- 悪意のあるリソースサーバー:クライアントが誤ってトークンを送り得る偽の RS
- 認可サーバーの mix-up:クライアントが複数の AS を使う際にレスポンスを混同させる攻撃
- 漏洩した認可リクエスト/レスポンス:ブラウザ履歴、ログ、リファラ経由の露出
この攻撃者モデルの下でもトークンの悪用とセッション乗っ取りが不可能であること — それが FAPI 2.0 の目標であり、実際に形式検証(formal analysis)によってセキュリティ特性が証明されています。「ベストプラクティス集」ではなく「証明されたセキュリティプロファイル」である点が FAPI 2.0 の最大の差別化要素です。
FAPI 1.0 から 2.0 へ — 簡素化の歴史
FAPI 1.0 は Baseline と Advanced の二つのプロファイルに分かれており、Advanced はハイブリッドフロー、JARM、リクエストオブジェクト署名など実装難度の高いメカニズムを要求していました。実装ごとに解釈が分かれ、適合性テストの合格に数か月かかることも珍しくありませんでした。
FAPI 2.0 は「セキュリティ水準は維持しつつ複雑さを下げる」という方向で再設計されました。
| 項目 | FAPI 1.0 Advanced | FAPI 2.0 Security Profile |
|------|-------------------|---------------------------|
| プロファイル構造 | Baseline + Advanced の二元化 | 単一の Security Profile + 任意の Message Signing |
| 認可リクエスト保護 | 署名付きリクエストオブジェクト(JAR)またはハイブリッドフロー | PAR に一元化 |
| 認可レスポンス保護 | JARM またはハイブリッドフローの ID トークン | authorization code + PKCE で十分 |
| レスポンスタイプ | code id_token などが混在 | code のみ |
| トークンバインディング | mTLS 中心 | DPoP または mTLS を選択 |
| 形式検証 | 部分的 | プロファイル全体の形式検証が完了 |
核心となる洞察はこうです。認可リクエストをフロントチャネル(ブラウザリダイレクト)で送る代わりにバックチャネルで事前登録(PAR)すれば、リクエスト改ざん防御のための複雑な署名装置が不要になります。同様に、PKCE と発行者識別(iss レスポンスパラメータ)がコード注入と mix-up を防ぐため、ハイブリッドフローの複雑さも排除できました。
FAPI 2.0 Security Profile の中核要件
FAPI 2.0 が認可サーバーとクライアントに要求する事項を圧縮すると次のとおりです。
1. PAR(Pushed Authorization Requests、RFC 9126)必須 — すべての認可リクエストをバックチャネルで事前登録
2. PKCE(S256)必須 — confidential クライアントでも例外なし
3. response_type は code のみ
4. sender-constrained アクセストークン必須 — DPoP または mTLS 証明書バインディング
5. クライアント認証は private_key_jwt または mTLS(tls_client_auth)— client_secret 系は禁止
6. iss レスポンスパラメータ(RFC 9207)で mix-up 攻撃を防御
7. refresh token も sender-constrained
8. 認可コードの 1 回限り使用、redirect_uri の完全一致など RFC 9700 水準の基本
フロー全体を図で見ると次のようになります。
+------------+ +---------------+
| クライアント | | 認可サーバー |
+----+-------+ +-------+-------+
| (1) POST /par |
| private_key_jwt + PKCE + リクエストパラメータ一式|
| ---------------------------------------------> |
| (2) request_uri(使い捨ての参照) |
| <--------------------------------------------- |
| |
| (3) ブラウザリダイレクト: client_id+request_uri |
| ---------------------------------------------> |
| (4) ユーザー認証/同意後 code + iss を返却 |
| <--------------------------------------------- |
| |
| (5) POST /token |
| code + code_verifier + private_key_jwt |
| (+ DPoP proof または mTLS) |
| ---------------------------------------------> |
| (6) sender-constrained アクセストークン |
| <--------------------------------------------- |
| |
| (7) API 呼び出し: token + DPoP proof/mTLS 証明書|
v v
PAR(RFC 9126)— 認可リクエストをバックチャネルへ
従来の認可リクエストはクエリストリングにすべてのパラメータを載せてブラウザ経由で送ります。この方式はパラメータが改ざんされ得るうえ、ブラウザ履歴やサーバーログに残り、URL 長の制限にも引っかかります。[PAR](https://datatracker.ietf.org/doc/html/rfc9126) は認可リクエストの本文をクライアント認証済みのバックチャネルで事前登録し、ブラウザには参照値(request_uri)だけを送る方式です。
POST /realms/fin/protocol/openid-connect/ext/par/request HTTP/1.1
Host: idp.bank.example
Content-Type: application/x-www-form-urlencoded
client_id=tpp-client
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJhbGciOiJQUzI1NiIs...
&response_type=code
&redirect_uri=https%3A%2F%2Ftpp.example%2Fcallback
&scope=accounts%20payments
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&state=af0ifjsldkj
レスポンスは次のとおりです。
{
"request_uri": "urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY22c",
"expires_in": 90
}
その後のブラウザリダイレクトは極端にシンプルになります。
GET /authorize?client_id=tpp-client
&request_uri=urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY22c
認可サーバーは登録時点ですでにクライアントを認証しパラメータを固定しているため、ブラウザ区間での改ざんは根本から遮断されます。request_uri は使い捨てで寿命も短い(通常 90 秒前後)ものです。
RAR(RFC 9396)— 取引レベルのきめ細かな認可
scope 文字列では「口座 A から B へ 15 万ウォン送金」のような取引レベルの認可を表現することが困難です。[RAR(Rich Authorization Requests)](https://datatracker.ietf.org/doc/html/rfc9396)は authorization_details という JSON 構造でこれを解決します。
{
"authorization_details": [
{
"type": "payment_initiation",
"instructedAmount": {
"currency": "KRW",
"amount": "150000"
},
"creditorAccount": {
"iban": "KR123456789012345678"
},
"remittanceInformation": "2026-06 家賃"
}
]
}
この構造は PAR リクエストに含まれてユーザー同意画面に取引内容そのまま表示され、発行されたアクセストークンにも authorization_details として刻印されます。リソースサーバーはトークン内の取引情報と実際の API リクエストが一致するかを検証します。「同意したものだけを、同意した分だけ」が暗号学的に強制されるのです。FAPI 2.0 において RAR は必須ではありませんが、決済シナリオでは事実上の標準的な組み合わせです。
Sender-Constrained Token — DPoP vs mTLS
FAPI 2.0 の最も重要な要件は bearer トークンの禁止、すなわち sender-constrained token の義務化です。トークンが漏洩しても正当な保有者だけが使用できなければなりません。選択肢は二つあります。
mTLS 証明書バインディング(RFC 8705)
[RFC 8705](https://datatracker.ietf.org/doc/html/rfc8705) は TLS クライアント証明書のサムプリント(thumbprint)をトークンにバインドします。トークンの cnf クレームに証明書ハッシュが入ります。
{
"sub": "user-1234",
"aud": "https://api.bank.example",
"cnf": {
"x5t#S256": "bwcK0esc3ACC3DB2Y5_lESsXE8o9ltc05O89jdN-dg2"
}
}
リソースサーバーは TLS ハンドシェイクで提示されたクライアント証明書のハッシュとトークンの cnf 値を比較します。TLS 層で自動的に強制されるためアプリケーションコードの変更は少ないものの、証明書の発行/ローテーションインフラ(PKI)と TLS 終端ポイントの管理が大変です。特にロードバランサーが TLS を終端する構成では、証明書情報をバックエンドへ伝える追加設定が必要です。
DPoP(RFC 9449)
[DPoP](https://datatracker.ietf.org/doc/html/rfc9449) はアプリケーション層で証明 JWT を毎リクエストに添付する方式です。クライアントが公開鍵/秘密鍵のペアを作り、トークンリクエストと API 呼び出しのたびにその鍵で署名した proof を DPoP ヘッダーで送ります。トークンには公開鍵のサムプリントが cnf の jkt としてバインドされます。
{
"sub": "user-1234",
"aud": "https://api.bank.example",
"cnf": {
"jkt": "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I"
}
}
選択基準の比較
| 基準 | mTLS(RFC 8705) | DPoP(RFC 9449) |
|------|------------------|------------------|
| 動作レイヤー | TLS(トランスポート) | HTTP(アプリケーション) |
| インフラ要件 | PKI、mTLS 対応の LB/ゲートウェイ | なし(鍵はクライアントが生成) |
| public クライアント(SPA/モバイル) | 事実上不可能 | 適している |
| クライアント認証の兼用 | 可能(tls_client_auth) | 不可(トークンバインディング専用) |
| プロキシ/CDN の通過 | TLS 終端ごとに設定が必要 | 透過的に通過 |
| 実装の複雑さ | インフラ側に集中 | クライアントコード側に集中 |
| 典型的な採用先 | 銀行間 B2B、閉域網 | フィンテックアプリ、public クライアント |
大まかにまとめると、サーバー間通信と既存 PKI がある環境では mTLS、モバイルアプリや SPA が絡む環境では DPoP が自然です。両者を併用するエコシステムも多くあります(クライアント認証は private_key_jwt、トークンバインディングは DPoP など)。
FAPI 2.0 Message Signing — 否認防止が必要なとき
Security Profile が「転送中の保護」を担当するなら、[FAPI 2.0 Message Signing](https://openid.net/specs/fapi-2_0-message-signing.html) は否認防止(non-repudiation)を追加します。紛争発生時に「このリクエスト/レスポンスが正確にこの内容でやり取りされた」を第三者に証明しなければならない規制環境のためのものです。
- 認可リクエストの署名:JAR(RFC 9101)によるリクエストオブジェクト署名
- 認可レスポンスの署名:JARM でレスポンスを署名付き JWT として受信
- リソースリクエスト/レスポンスの署名:HTTP Message Signatures(RFC 9421)を活用
Message Signing は必要なメッセージタイプにのみ選択的に適用できます。ブラジルのオープンファイナンスのように規制が要求する市場で主に採用されています。
Keycloak 26.6 で FAPI 2.0 を構成する
Keycloak は client policies メカニズムで FAPI 要件を強制します。26.6 は FAPI 2.0 Security Profile と Message Signing の Final 仕様に対応する組み込みプロファイルを提供します。組み込みグローバルプロファイル名は fapi-2-security-profile、fapi-2-message-signing です。
client policy は「条件(どのクライアントに)」+「プロファイル(どの要件を)」の組み合わせです。例えば、特定のクライアントロールを持つクライアント全体に FAPI 2.0 を強制するポリシーを JSON で定義すると次のようになります。
{
"policies": [
{
"name": "fapi2-for-openbanking-clients",
"description": "Enforce FAPI 2.0 Security Profile on all open banking clients",
"enabled": true,
"conditions": [
{
"condition": "client-roles",
"configuration": {
"roles": ["openbanking-tpp"]
}
}
],
"profiles": ["fapi-2-security-profile"]
}
]
}
Admin CLI で適用します。
現在のポリシーを確認
kcadm.sh get client-policies/policies -r fin
ポリシーを更新(上記 JSON を policies.json として保存した想定)
kcadm.sh update client-policies/policies -r fin -f policies.json
このポリシーが適用されたクライアントが FAPI 要件に違反すると — 例えば PAR なしで認可リクエストを行う、client_secret で認証する、PKCE を省略する — Keycloak はリクエスト自体を拒否します。クライアント登録時点でも executor が動作し、非準拠の設定(例:許可されていない署名アルゴリズム)をブロックします。
あわせて点検すべき realm レベルの設定です。
PAR リクエストの寿命(デフォルト 60 秒、FAPI 推奨範囲内を維持)
kcadm.sh update realms/fin -s 'attributes."parRequestUriLifespan"="90"'
DPoP 有効化の確認(26.x でサポート)
kcadm.sh get realms/fin --fields attributes
クライアントに mTLS トークンバインディングを強制
kcadm.sh update clients/CLIENT-UUID -r fin \
-s 'attributes."tls.client.certificate.bound.access.tokens"="true"'
26.6 では EdDSA 署名のサポートも追加され、今後のアルゴリズムポリシー策定時の選択肢が広がりました。ただし FAPI 2.0 が許可するアルゴリズムは PS256、ES256 系が中心であるため、エコシステムの要件を先に確認すべきです。
適合性テスト — Conformance Suite
FAPI の大きな利点は「準拠しているか」を機械的に検証できることです。OpenID Foundation が提供する [conformance suite](https://www.certification.openid.net/) は、認可サーバーとクライアントに対して数百のシナリオ(正常フロー、改ざんされたリクエスト、不正な署名、期限切れトークンなど)を自動実行します。
運用観点のヒントです。
1. 認証(certification)の前にローカルでスイートを回してみてください。スイートはオープンソースで Docker で起動できます。
2. テスト対象環境は本番と同一の TLS/プロキシ構成であるべきです。LB がヘッダーを書き換えることによる失敗が多発します。
3. 失敗メッセージは仕様の条項番号とともに出力されるため、仕様文書を手元に置いて読むのが最速です。
4. Keycloak は FAPI 適合性認証を受けた実装ですが、あなたのデプロイ構成(リバースプロキシ、カスタム SPI)が適合性を壊す可能性があります。デプロイ単位で再検証するのが安全です。
韓国金融業界の観点 — マイデータ API と FAPI
韓国のマイデータ(本人信用情報管理業)エコシステムは、金融保安院主導の標準 API 規格を使用しています。現行規格は FAPI をそのまま採用したものではありませんが、構造的に類似した要素が多くあります — 機関間の相互認証、転送区間の暗号化、アクセストークンベースの認可、情報提供同意の細分化などです。
FAPI 2.0 の観点からマイデータ類のシステムを設計/改善する際に得られるポイントは次のとおりです。
- 同意内容のトークンバインディング:RAR の authorization_details のように同意範囲をトークンに構造的に埋め込む設計は、「同意外照会」事故を技術的に遮断します。
- bearer トークンからの脱却:機関間 API では mTLS バインディング、ユーザー端末が絡む区間では DPoP を検討できます。
- 国際的整合性:海外オープンバンキングとの相互運用やグローバルフィンテック進出を考慮するなら、国内規格の上に FAPI 2.0 互換レイヤーを置く戦略が有効です。
- 適合性テスト文化:規格文書 + 手動点検中心から、conformance suite スタイルの自動化された相互運用性検証へ移行することが、エコシステム全体のコストを下げます。
一般的なサービスが FAPI から学べること
あなたのサービスが金融でなくても、FAPI 2.0 は「OAuth を正しく使えばどこまで行けるか」のリファレンスです。費用対効果の良い順に絞ると次のとおりです。
1. PKCE の全面適用 — OAuth 2.1 ではどのみち義務です。今すぐ有効化しましょう。
2. redirect_uri の完全一致、code の 1 回限り使用 — 設定変更だけで可能なレベルです。
3. PAR の導入 — クライアント変更が必要ですが、認可リクエストの改ざんとログ漏洩の問題を構造的に排除します。
4. private_key_jwt へのクライアント認証移行 — 共有シークレットの漏洩/ローテーション問題から解放されます。
5. 高リスク API に限定した DPoP — 全面適用が負担なら決済/個人情報 API から。
逆に、Message Signing や mTLS の全面導入は、規制要求がなければ過剰投資になり得ます。FAPI 2.0 自体が「必要な分だけ」の哲学で簡素化されたプロファイルであることを覚えておいてください。
グローバルエコシステムの現況と段階的導入ロードマップ
FAPI 2.0 を採用済み、または移行中の主要エコシステムを比較すると次のとおりです。
| エコシステム | 基盤プロファイル | トークンバインディング | 特徴 |
|-------------|----------------|---------------------|------|
| 英国 Open Banking | FAPI 1.0 Advanced から 2.0 へ移行中 | mTLS 中心 | 最も歴史ある大規模運用事例 |
| ブラジル Open Finance | FAPI 2.0 + Message Signing | mTLS | 否認防止要件から署名を採用 |
| オーストラリア CDR | FAPI 1.0 ベース、2.0 ロードマップ | mTLS | 銀行以外にエネルギーなど産業拡大 |
| サウジアラビア | FAPI 2.0 | mTLS/DPoP | 最初から 2.0 で設計 |
新規導入組織向けの段階的ロードマップを提案します。
Phase 0 現状把握
- クライアント一覧、認証方式、PKCE 適用率の調査
Phase 1 基盤固め(互換性への影響なし)
- PKCE S256 を全クライアントに適用
- redirect_uri の完全一致、code の 1 回限り使用を確認
- RFC 9207 iss パラメータ検証を追加
Phase 2 クライアント認証の移行
- client_secret -> private_key_jwt マイグレーション
- JWKS エンドポイントの運用、鍵ローテーション手順の確立
Phase 3 リクエスト保護
- PAR 導入、require_pushed_authorization_requests を強制
Phase 4 トークンバインディング
- パイロットクライアントに DPoP または mTLS を適用
- リソースサーバーに cnf 検証をデプロイ
Phase 5 ポリシー化と検証
- Keycloak client policies で全体に強制
- conformance suite の回帰テストパイプラインを構築
各 Phase は単独でも価値があるため、全体の完了前でもセキュリティレベルは段階的に上がります。
リソースサーバーでの cnf 検証の実装
FAPI のラストワンマイルはリソースサーバーです。Spring ベースのリソースサーバーで mTLS バインディングトークンの cnf を検証する例です。
public class CertificateBoundTokenValidator
implements OAuth2TokenValidator<Jwt> {
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
Map<String, Object> cnf = jwt.getClaim("cnf");
if (cnf == null || cnf.get("x5t#S256") == null) {
return OAuth2TokenValidatorResult.failure(
new OAuth2Error("invalid_token",
"cnf claim missing - sender-constrained token required",
null));
}
String boundThumbprint = (String) cnf.get("x5t#S256");
// プロキシが転送したクライアント証明書からサムプリントを計算
String presentedThumbprint = CertificateThumbprintHolder.current();
if (!boundThumbprint.equals(presentedThumbprint)) {
return OAuth2TokenValidatorResult.failure(
new OAuth2Error("invalid_token",
"certificate thumbprint mismatch", null));
}
return OAuth2TokenValidatorResult.success();
}
}
核心は「cnf がなければ拒否」です。sender-constrained を義務化した環境で cnf のないトークンを通過させると、ダウングレード攻撃の経路が開きます。
トラブルシューティングとアンチパターン
FAPI プロファイルを適用する際によく遭遇する問題です。
症状 原因と対処
----------------------------------------------------------------
invalid_request: PAR required クライアントが request_uri なしで
/authorize に直行。クライアント SDK
の PAR 対応を確認。
invalid_client(PAR 段階) private_key_jwt の aud/exp エラー、
JWKS 未登録または鍵ローテーション後に
旧鍵を使用。
invalid_dpop_proof proof の htm/htu 不一致(プロキシの
URL 書き換え)、iat の時計ずれ、jti 再利用。
cnf 不一致による 401 LB が TLS 終端後に証明書を未転送。
プロキシで証明書ヘッダー転送の設定が必要。
PKCE 検証失敗 code_verifier の保存場所の問題(マルチ
インスタンス環境の非スティッキーセッション)。
アンチパターンも挙げておきます。
- FAPI ポリシーを一部のクライアントだけに適用し、同じ API を非 FAPI クライアントにも開放すること — セキュリティレベルは最も弱い経路に収束します。
- request_uri の寿命を長く延ばすこと — PAR の使い捨て性/短命性はセキュリティ根拠の一部です。
- conformance に一度合格した後、再検証なしで構成を変更すること — プロキシ/アルゴリズム/SPI の変更のたびに回帰テストが必要です。
おわりに
FAPI 2.0 は「強い攻撃者モデルの下で形式検証された、それでいて実装可能な」セキュリティプロファイルという点で、OAuth エコシステムの重要なマイルストーンです。要約します。
- FAPI 1.0 の複雑さ(ハイブリッドフロー、JARM 必須)は PAR + PKCE + code フローに簡素化されました。
- sender-constrained token が核心です。インフラの成熟度に応じて mTLS または DPoP を選択してください。
- Keycloak 26.6 の組み込み client policies で FAPI 2.0 Final を宣言的に強制できます。
- 適合性は conformance suite で機械的に検証し、デプロイ構成の変更のたびに回帰させてください。
- 金融でなくても PKCE、PAR、private_key_jwt は普遍的な利益です。
高い保証レベルの認可が必要なすべての場所で、FAPI 2.0 はすでに検証済みの出発点を提供します。車輪を再発明する前に、まずこのプロファイルを読むことをお勧めします。
参考資料
- [FAPI 2.0 Security Profile](https://openid.net/specs/fapi-2_0-security-profile.html)
- [FAPI 2.0 Attacker Model](https://openid.net/specs/fapi-2_0-attacker-model.html)
- [FAPI 2.0 Message Signing](https://openid.net/specs/fapi-2_0-message-signing.html)
- [RFC 9126 — OAuth 2.0 Pushed Authorization Requests](https://datatracker.ietf.org/doc/html/rfc9126)
- [RFC 9396 — OAuth 2.0 Rich Authorization Requests](https://datatracker.ietf.org/doc/html/rfc9396)
- [RFC 9449 — OAuth 2.0 Demonstrating Proof of Possession (DPoP)](https://datatracker.ietf.org/doc/html/rfc9449)
- [RFC 8705 — OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens](https://datatracker.ietf.org/doc/html/rfc8705)
- [RFC 9207 — OAuth 2.0 Authorization Server Issuer Identification](https://datatracker.ietf.org/doc/html/rfc9207)
- [RFC 9700 — Best Current Practice for OAuth 2.0 Security](https://datatracker.ietf.org/doc/html/rfc9700)
- [RFC 7636 — Proof Key for Code Exchange (PKCE)](https://datatracker.ietf.org/doc/html/rfc7636)
- [OpenID Foundation Conformance Suite](https://www.certification.openid.net/)
- [Keycloak Documentation](https://www.keycloak.org/documentation)
- [Keycloak Release Notes](https://www.keycloak.org/docs/latest/release_notes/index.html)
- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
현재 단락 (1/264)
API がひとつ破られると口座振替が発生する世界があります。オープンバンキング、マイデータ、オープンファイナンスがその世界です。このような環境では「一般的な OAuth 2.0 ベストプラクティス」だ...