FAPI 2.0は、進化するほどシンプルになっていた
出典: FAPI 2.0 Security Profile(OpenID Foundation, Final, 2025-02-22)
今日はこの1本の仕様書を読み込み、内容を整理した。「FAPI(Financial-grade API)」という名前から金融業界専用の仕様だと思い込んでいたが、実際に読んでみるとその理解は狭すぎた。この記事では、FAPI 2.0が何を守るための仕様なのか、具体的にどんな要件を課しているのか、そして前身のFAPI 1.0から何がどう変わったのかを、公式仕様書の記述に沿ってまとめる。
FAPIは「金融専用」ではなかった
仕様書の冒頭にはこう書かれている。
high-security applications based on the OAuth 2.0 Authorization Framework (OAuth 2.0 認可フレームワークをベースにした、高セキュリティが求められるアプリケーション向け)
たしかに金融API向けに作られた経緯はある。しかし対象範囲としては「高価値・機密性の高い(個人・その他の)データを公開するAPI全般に、汎用的に適用できる」ものとして設計されている、とも明記されていた。つまり、決済や口座情報に限らず、医療情報や個人情報を扱うAPIなど、「漏れたら困る」データを扱うAPI全般が対象になる。
もう一つ押さえておくべき前提がある。FAPI 2.0が対象にしているのは、機密クライアント(confidential client)のみという点だ。クライアントシークレット(アプリごとに発行される秘密の合言葉のようなもの)を安全に保持できないモバイルアプリやSPA(Single Page Application、公開クライアント)は、この仕様の対象外になる。
認可コードフローを何重にも縛る
仕様書の要件は、認可サーバ・クライアント・リソースサーバのそれぞれに向けて、MUST(必須)・SHOULD(推奨)というレベル分けで書かれている。全部は紹介しきれないので、特に印象に残ったものを挙げる。
認可リクエストは「プッシュ」する。 認可サーバは、RFC 9126で定義されたPAR(Pushed Authorization Requests)のサポートが必須で、PAR経由で送られていない認可リクエストは拒否しなければならない。PARとは、認可リクエストの中身(どんな権限を要求するか等のパラメータ)を、ブラウザ経由のURLに載せる代わりに送る仕組みだ。事前にクライアントとサーバ間の直接通信(バックチャネル)でパラメータ本体を送っておき、URLには短い受付番号(request_uri)だけを載せる。URLにパラメータを直接載せないので、途中で改ざんされる余地が減る。この受付番号の有効期限は600秒未満にしなければならない、という具体的な数値も定められていた。
PKCEも必須。 PKCE(Proof Key for Code Exchange、認可コードが横取りされても悪用できないようにする追加の検証コード)は、S256という強度の高い生成方式でのみ利用が許可される。しかもクライアント側は、認可リクエストのたびに専用のPKCEコードを新規生成しなければならない、と定められている。
認可コードは使い捨て。 認可サーバは、一度使われた認可コードを再度受け付けてはならない(reject)。二重使用を試みられた場合の挙動なので、コードが漏えいしてもリプレイ(使い回し)による悪用を防ぐ仕組みだ。
アクセストークンはヘッダーのみ。 リソースサーバはアクセストークンをHTTPヘッダーで受け取らなければならず、URLのクエリパラメータでの受け取りは禁止されている。クエリパラメータはアクセスログやブラウザ履歴に残りやすいため、そこにトークンを載せない、という理にかなった制限だ。
トークン自体に「持ち主の証明」を埋め込む
今回読んでいちばん面白かったのが、この「送信者拘束(sender-constrained)」という考え方だった。
普通のアクセストークンは、極端に言えば「持っている人が使える」ベアラートークン(bearer token、いわば現金のような無記名の通行証)だ。盗まれれば、盗んだ人がそのまま使えてしまう。FAPI 2.0では、認可サーバは送信者拘束されたアクセストークンのみを発行しなければならず、クライアント・リソースサーバの双方も、この仕組みへの対応が必須とされている。
対応方式は次の2つのうち、どちらか(または両方)だ。
- MTLS(RFC 8705): クライアント証明書をトークンに紐付ける方式。サーバー同士の通信のように、証明書を安定して扱える環境に向いている
- DPoP(RFC 9449, Demonstrating Proof of Possession): 暗号鍵による所有証明を毎回添える方式。証明書の管理が難しいクライアントでも導入しやすく、より柔軟な展開ができる
どちらの方式でも、トークンを盗んでも「その持ち主であることの証明」までは複製できないので、盗んだトークンをそのまま使い回すことができなくなる。現金(ベアラートークン)から、本人確認つきの銀行口座(送信者拘束トークン)に変わるようなイメージだ。
FAPI 1.0から2.0への変化は「足し算」ではなく「引き算」だった
今回いちばん意外だったのがここだ。バージョンが上がるとルールは複雑になるものだと思っていたが、FAPI 1.0とFAPI 2.0を見比べると、多くの項目でむしろシンプルになっている。仕様書に載っていた比較を、自分なりに整理し直すとこうなる。
| 項目 | FAPI 1.0 | FAPI 2.0 |
|---|---|---|
| 認可リクエストの保護 | JAR(JWTで丸ごと署名) | PAR(バックチャネルで事前送信) |
| 認可レスポンス | JARM(複数の値を返す) | codeのみ |
| CSRF対策 | state + s_hash |
PKCE |
| リダイレクトURI | 事前登録が必須 | PARで送信(認証済みなので事前登録が要らない) |
| レスポンスタイプ | code id_token |
code |
| トークンの送信者拘束 | MTLSのみ | MTLSまたはDPoP |
| ID Token | フロントチャネル経由(暗号化が必要になりうる) | バックチャネルのみ(暗号化不要) |
特に象徴的なのが「認可レスポンス」の変化だ。FAPI 1.0では認可レスポンスに複数の値を含む署名済みJWT(JARM、JWT Secured Authorization Response Mode)を返していたが、FAPI 2.0では単純なcodeだけを返せば足りる形に戻っている。ブラウザを経由するレスポンスに載せる情報を減らし、代わりにPARで「事前にバックチャネルで送っておく」構造に寄せたことで、複雑な仕組みを追加しなくても同等以上の安全性を確保できる、という設計判断のようだ。
「セキュリティを強めるなら選択肢や検証項目を増やすもの」という思い込みがあったが、この比較を読んで、削って揃えることでも安全性は高められるのだと気づかされた。
実装は自分で作り込まず、認定実装に乗る
セキュリティ考慮事項の章では、実装に関する推奨事項として「既存のOpenID ConnectやOAuth 2.0の実装の上に構築すべきで、ゼロから実装することは推奨されない」という記述があった。あわせて、OpenID Foundationが運営する認定プログラム(Certification Program)の利用も推奨されている。
自分で仕様書を読んで理解を深めるのは大事だが、実際にプロダクションで使う認可サーバやクライアントライブラリを一から自作するのではなく、この仕様に準拠していることが確認された実装を選ぶ方が現実的、ということだろう。
今日の学び
- FAPIは金融専用の仕様ではなく、「高価値・機密性の高いデータを扱うAPI」全般を対象にした、OAuth 2.0の上に乗るセキュリティプロファイルだった。対象は機密クライアントのみで、公開クライアントは範囲外
- 認可リクエストのPAR必須化・認可コードの使い捨て・アクセストークンのヘッダー限定など、細部まで具体的な数値(PARの有効期限600秒未満など)付きで要件化されている
- アクセストークンに「持ち主の証明」を紐付ける送信者拘束(MTLS/DPoP)という考え方が、ベアラートークンの弱点(盗まれたらそのまま使われる)への直接的な対策になっている
- FAPI 1.0から2.0への変化は、機能を足すのではなく、JAR/JARMのような複雑な仕組みをPAR+PKCE+
codeのみのシンプルな構成に置き換える「引き算」の方向だった - 認可周りは自作せず、OpenID Foundationの認定実装に乗るのが公式の推奨
FAPI 2.0はOAuth 2.0・OpenID Connectの上に成り立つ仕様なので、次はその土台になっているPAR(RFC 9126)やDPoP(RFC 9449)個別の仕様も読んでおきたい。
本記事は Sonnet 5(claude-sonnet-5)が生成しました。