FAPI CIBA Profile - プロトコル仕様
概要
FAPI CIBA (Financial-grade API: Client Initiated Backchannel Authentication) Profile は、金融グレードのセキュリティを備えたバックチャネル認証を実現するためのプロファイルです。
idp-serverは OpenID FAPI CIBA Security Profile に完全準拠しており、以下の3つの仕様を組み合わせています:
- FAPI Part 1 (Baseline): 読み取り専用APIの基本セキュリティ
- FAPI Part 2 (Advanced): 書き込みAPIの高度なセキュリティ
- CIBA Core: バックチャネル認証フロー
ユースケース
FAPI CIBAは以下のような高セキュリティが要求されるシナリオで使用されます:
| ユースケース | 説明 | 具体例 |
|---|---|---|
| オープンバンキング | PSD2準拠の金融API | 口座情報参照、送金実行 |
| デバイス分離認証 | 操作端末と認証端末が異なる | ATM送金、コールセンター認証 |
| リモート承認 | 別デバイスでの認証承認 | スマホでのプッシュ通知承認 |
| 高額取引 | 金融取引の多要素認証 | 大口送金、証券取引 |
FAPI CIBA vs 標準CIBA
セキュリティ要件比較
| 項目 | 標準CIBA | FAPI CIBA |
|---|---|---|
| リクエストオブジェクト | 任意 | 必須(署名付き) |
| 署名アルゴリズム | RS256等も可 | PS256/ES256のみ |
| リクエスト有効期限 | 制限なし | 最大60分 |
| クライアント認証 | client_secret系も可 | private_key_jwt, mTLS のみ |
| binding_message | 任意 | authorization_details未使用時は必須 |
| トークン配信モード | push/poll/ping | poll/pingのみ(push禁止) |
| トークンバインディング | 任意 | Sender-constrained必須(mTLS) |
| aud claim | 任意 | 必須(Issuer URL含む) |
プロトコルフロー
1. バックチャネル認証リクエスト
POST /backchannel/authentications HTTP/1.1
Host: idp-server.example.com
Content-Type: application/x-www-form-urlencoded
request=eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNlbGZfcmVxdWVzdF9rZXlfcHMyNTYifQ...
&client_id=fapi-ciba-client
リクエストオブジェクト(JWT)の内容:
{
"alg": "PS256",
"kid": "self_request_key_ps256"
}
.
{
"scope": "openid payment_initiation",
"binding_message": "TX-12345: €500 to ACME Corp",
"user_code": "123456",
"login_hint": "device:auth_device_id,idp:idp-server",
"authorization_details": [
{
"type": "payment_initiation",
"instructedAmount": {
"currency": "EUR",
"amount": "500.00"
},
"creditorName": "ACME Corp",
"creditorAccount": {
"iban": "DE02100100109307118603"
}
}
],
"client_id": "fapi-ciba-client",
"aud": "https://idp-server.example.com",
"iss": "fapi-ciba-client",
"exp": 1234567890,
"iat": 1234564290,
"nbf": 1234564290,
"jti": "unique-request-id"
}
レスポンス:
{
"auth_req_id": "ea2856d7-9aab-40c6-ae71-f8db93602eab",
"expires_in": 600,
"interval": 5
}
2. 認証デバイスでの承認
ユーザーはスマートフォン等の認証デバイスでプッシュ通知を受け取り、以下の情報を確認:
- binding_message: "TX-12345: €500 to ACME Corp"
- authorization_details: 支払い詳細(金額、送金先等)
生体認証(FIDO-UAF)またはパスワードで承認します。
3. トークンリクエスト(ポーリング)
POST /tokens HTTP/1.1
Host: idp-server.example.com
Content-Type: application/x-www-form-urlencoded
SSL-Client-Cert: <client-certificate>
grant_type=urn:openid:params:grant-type:ciba
&auth_req_id=ea2856d7-9aab-40c6-ae71-f8db93602eab
&client_id=fapi-ciba-client
レスポンス:
{
"access_token": "eyJhbGc...",
"id_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "refresh..."
}
アクセストークン(JWT)の内容:
{
"iss": "https://idp-server.example.com",
"sub": "user-123",
"aud": "resource-server",
"exp": 1234567890,
"iat": 1234564290,
"scope": "openid payment_initiation",
"cnf": {
"x5t#S256": "bwcK0esc3ACC3DB2Y5_lESsXE8o9ltc05O89jdN-dg2"
}
}
重要: cnf:x5t#S256 がクライアント証明書のサムプリントで、mTLSバインディングを実現します。
FAPI要件マッピング
要件の階層構造
FAPI CIBAは 3つの仕様を積み重ねた構造 になっています:
┌─────────────────────────────────────────┐
│ FAPI CIBA 5.2.2 │ ← CIBA固有の追加要件
│ - binding_message要件 │
│ - Push mode禁止 │
│ - Signed request object必須 │
├─────────────────────────────────────────┤
│ FAPI Part 2 (Advanced) 5.2.2 │ ← Part 1の一部を上書き
│ - Client authentication制限(上書き) │
│ - Sender-constrained tokens必須 │
│ - Request object lifetime制限 │
├─────────────────────────────────────────┤
│ FAPI Part 1 (Baseline) 5.2.2 │ ← 基本セキュリティ要件
│ - Confidential client必須 │
│ - 鍵サイズ・エントロピー要件 │
│ - トークン推測不可能性 │
├─────────────────────────────────────────┤
│ 基本OIDC/OAuth 2.0 │ ← 標準プロトコル
└─────────────────────────────────────────┘
FAPI CIBA準拠 = すべての層の要件を満たす
重要な上書き関係
| 要件 | FAPI Part 1 | FAPI Part 2(上書き) | 最終適用 |
|---|---|---|---|
| Client Authentication | private_key_jwtclient_secret_jwt ✅mTLS | private_key_jwtclient_secret_jwt ❌mTLS | Part 2が優先 secret系は禁止 |
| PKCE | 必須(5.2.2-7) | 免除(CIBAには不適用) | Part 2が優先 CIBA不要 |
FAPI Part 1 (Baseline) 5.2.2 要件
| # | 要件 | CIBA適用 | 実装 | 備考 |
|---|---|---|---|---|
| 1 | Confidential clients必須 | ✅ 適用 | throwExceptionIfNotConfidentialClient() | Public client禁止 |
| 2 | Public clients推奨 | ❌ 不適用 | - | FAPI CIBAはconfidentialのみ |
| 3 | Client secret entropy (OIDC 16.19) | ✅ 適用 | - | 256/512 bit minimum |
| 4 | Client authentication methods | ⚠️ 上書き | throwExceptionIfInvalidClientAuthenticationMethod() | FAPI Part 2で変更 |
| 5 | RSA key ≥ 2048 bits | ✅ 適用 | JWKs検証 | Request object署名、private_key_jwt認証 |
| 6 | EC key ≥ 160 bits | ✅ 適用 | JWKs検証 | 実際はES256(256 bits)が最小 |
| 7 | PKCE with S256 | ❌ 不適用 | - | CIBAにはauthorization codeなし |
| 8 | Pre-register redirect_uri | ❌ 不適用 | - | CIBAにはredirectなし |
| 9 | Require redirect_uri in request | ❌ 不適用 | - | 同上 |
| 10 | Exact match redirect_uri | ❌ 不適用 | - | 同上 |
| 11 | User authentication LoA | ✅ 適用 | Authentication Policy | 認証ポリシーで制御 |
| 12 | Explicit user approval | ✅ 適用 | - | binding_message/authorization_details |
| 13 | Reject reused authorization code | ❌ 不適用 | - | CIBAにはcodeなし |
| 14 | Token response format (RFC6749 4.1.4) | ✅ 適用 | - | 標準準拠 |
| 15 | Return granted scopes (front channel) | ❌ 不適用 | - | CIBAはbackchannel |
| 16 | Non-guessable tokens (RFC6749 10.10) | ✅ 適用 | - | auth_req_id, tokens |
| 17 | Grant details disclosure (OIDC 16.18) | ✅ 適用 | - | binding_message |
| 18 | Token revocation (OIDC 16.18) | ✅ 適用 | - | Revocation endpoint |
| 19 | invalid_client for mismatched client_id | ✅ 適用 | - | エラーハンドリング |
| 20 | HTTPS redirect_uri | ❌ 不適用 | - | CIBAにはredirectなし |
| 21 | Access token lifetime < 10min | ⚠️ 推奨 | - | Sender-constrainedなら任意 |
| 22 | OIDD support | ✅ 適用 | - | Discovery必須 |
FAPI Part 2 (Advanced) 5.2.2 要件 - 完全版
FAPI Part 2 (Advanced) は 書き込みAPI向けの高度なセキュリティプロファイル で、PAR (Pushed Authorization Request)、JARM (JWT Secured Authorization Response Mode)、より強力なクライアント認証を追加しています。
CIBA適用時の注意: FAPI Part 2の多くの要件はフロントチャネル(authorization endpoint)向けですが、以下の要件がCIBAに適用されます。
Authorization Server要件(全18要件)
| # | FAPI Part 2 要件(原文) | CIBA適用 | 実装状況 | 実装メソッド/場所 | 備考 |
|---|---|---|---|---|---|
| 前提 | FAPI Part 1準拠(5.2.2-7除く) | ⚠️ 部分適用 | ✅ 実装済み | - | PKCE要件(5.2.2-7)はCIBA不適用 |
| 5.2.2-1 | shall require a JWS signed JWT request object passed by value with the request parameter or by reference | ✅ 適用 | ✅ 実装済み | FapiCibaVerifier.throwExceptionIfNotSignedRequestObject() | CIBAではrequestパラメータによる値渡しのみ(request_uriは不使用) |
| 5.2.2-2 | shall require either response_type value code id_token OR code in conjunction with response_mode jwt | ❌ 不適 用 | N/A | - | CIBAにはresponse_typeパラメータなし(backchannel) |
| 5.2.2-5 | shall only issue sender-constrained access tokens | ✅ 適用 | ✅ 実装済み | FapiCibaVerifier.throwIfNotSenderConstrainedAccessToken() | mTLSバインディング必須(cnf:x5t#S256) |
| 5.2.2-6 | shall support MTLS as mechanism for constraining the legitimate senders of access tokens | ✅ 適用 | ✅ 実装済み | mTLS証明書バインディング | cnf:x5t#S256クレームによる証明書サムプリント |
| 5.2.2-10 | shall only use the parameters included in the signed request object passed via request or request_uri | ✅ 適用 | ✅ 実装済み | FapiCibaVerifier.throwExceptionIfNotSignedRequestObject() | 署名済みrequest objectのみ使用、外部パラメータ無視 |
| 5.2.2-11 | may support pushed authorization request endpoint (PAR) | ❌ 不適用 | N/A | - | CIBAには/backchannel/authenticationsが専用エンドポイント(PAR不要) |
| 5.2.2-13 | shall require the request object to contain an exp claim that has a lifetime of no longer than 60 minutes after nbf | ✅ 適用 | ✅ 実装済み | FapiCibaVerifier.throwExceptionIfInvalidRequestObjectLifetime() | exp - nbf ≤ 60分 |
| 5.2.2-14 | shall authenticate confidential clients via MTLS or private_key_jwt methods | ✅ 適用 | ✅ 実装済み | FapiCibaVerifier.throwExceptionIfInvalidClientAuthenticationMethod() | 重要: Part 1の5.2.2-4を上書き(client_secret_jwt禁止) |
| 5.2.2-15 | shall require the aud claim in the request object to be the OP's Issuer Identifier URL | ✅ 適用 | ✅ 実装済み | FapiCibaVerifier.throwExceptionIfNotContainsAud() | audにIssuer URL含有を検証 |
| 5.2.2-16 | shall not support public clients | ✅ 適用 | ✅ 実装済み | FapiCibaVerifier.throwExceptionIfNotConfidentialClient() | Confidential clientのみ許可 |
| 5.2.2-17 | shall require the request object to contain an nbf claim that is no longer than 60 minutes in the past | ✅ 適用 | ✅ 実装済み | FapiCibaVerifier.throwExceptionIfInvalidRequestObjectLifetime() | now - nbf ≤ 60分 |
| 5.2.2-18 | shall require PAR requests to use PKCE with S256 as the code challenge method | ❌ 不適用 | N/A | - | CIBAにはPAR不要(5.2.2-11参照) |
CIBA適用要件のみ抽出(8要件)
| # | FAPI Part 2 要件 | 実装状況 | 実装メソッド | テストケース |
|---|---|---|---|---|
| 5.2.2-1 | JWS signed request object必須 | ✅ 実装済み | throwExceptionIfNotSignedRequestObject() | fapi_ciba.test.js 署名検証テスト |
| 5.2.2-5 | Sender-constrained tokens必須 | ✅ 実装済み | throwIfNotSenderConstrainedAccessToken() | Test #21: cnf:x5t#S256検証 |
| 5.2.2-6 | MTLSサポート | ✅ 実装済み | mTLS証明書バインディング | 全CIBA E2EテストでmTLS使用 |
| 5.2.2-10 | 署名済みrequest objectのみ使用 | ✅ 実装済み | throwExceptionIfNotSignedRequestObject() | 同5.2.2-1 |
| 5.2.2-13 | Request object lifetime ≤ 60min | ✅ 実装済み | throwExceptionIfInvalidRequestObjectLifetime() | Test #8: exp-nbf検証 |
| 5.2.2-14 | Client authentication (上書き) | ✅ 実装済み | throwExceptionIfInvalidClientAuthenticationMethod() | Test #9: 認証方式検証 |
| 5.2.2-15 | aud claim必須 | ✅ 実装済み | throwExceptionIfNotContainsAud() | FAPI Part 2 5.2.2-11テスト |
| 5.2.2-16 | Public clients禁止 | ✅ 実装済み | throwExceptionIfNotConfidentialClient() | Test #2: confidential client検証 |
| 5.2.2-17 | nbf claim検証 | ✅ 実装済み | throwExceptionIfInvalidRequestObjectLifetime() | Test #10: nbf age検証 |
実装完了度サマリー
FAPI Part 2 (Advanced) 5.2.2要件: 全18要件
├─ CIBA適用: 9要件(前提1 + 本文8)
│ ├─ 実装済み: 9要件 (100%)
│ ├─ 未実装: 0要件
│ └─ テストカバレッジ: 9/9 (100%)
└─ CIBA不適用: 9要件
└─ 理由: フロントチャネル専用(authorization endpoint、PAR等)
結論: FAPI Part 2 (Advanced) のCIBA適用要件は 完全実装済み です。
5.2.2-14: Client Authentication Methods(重要な上書き)
FAPI Part 1 (5.2.2-4): 以下のいずれかを許可
private_key_jwtclient_secret_jwt✅- mTLS (
tls_client_auth,self_signed_tls_client_auth)
FAPI Part 2 (5.2.2-14): 以下のみ許可(client_secret_jwt禁止)
private_key_jwt✅- mTLS (
tls_client_auth,self_signed_tls_client_auth) ✅
実装:
void throwExceptionIfInvalidClientAuthenticationMethod(CibaRequestContext context) {
ClientAuthenticationType authenticationType = context.clientAuthenticationType();
boolean isValid =
authenticationType.isPrivateKeyJwt()
|| authenticationType.isTlsClientAuth()
|| authenticationType.isSelfSignedTlsClientAuth();
if (!isValid) {
throw new BackchannelAuthenticationUnauthorizedException(
"invalid_client",
String.format(
"FAPI CIBA Profile requires client authentication method to be one of: private_key_jwt, tls_client_auth, self_signed_tls_client_auth. Current method: %s",
authenticationType.name()));
}
}
エラー例:
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"error": "invalid_client",
"error_description": "FAPI CIBA Profile requires client authentication method to be one of: private_key_jwt, tls_client_auth, self_signed_tls_client_auth. Current method: client_secret_post"
}
FAPI CIBA 5.2.2 固有要件
| # | 要件 | 実装 | HTTPステータス |
|---|---|---|---|
| 1 | Signed request object必須 | throwExceptionIfNotSignedRequestObject() | 400 |
| 2 | Request object lifetime ≤ 60min | throwExceptionIfInvalidRequestObjectLifetime() | 400 |
| 3 | Signing algorithm PS256/ES256 | throwExceptionIfInvalidSigningAlgorithm() | 400 |
| 4 | Confidential clients | throwExceptionIfNotConfidentialClient() | 401 |
| 5 | binding_message (条件付き) | throwExceptionIfMissingBindingMessage() | 400 |
| 6 | Push mode禁止 | throwExceptionIfPushMode() | 400 |
| 7 | Client authentication restrictions | throwExceptionIfInvalidClientAuthenticationMethod() | 401 |
| 11 | aud claim必須 | throwExceptionIfNotContainsAud() | 400 |
| - | Sender-constrained tokens | throwIfNotSenderConstrainedAccessToken() | 400 |
エラーレスポンス
invalid_client (HTTP 401)
CIBA Core Section 13準拠: クライアント認証失敗は401を返す
{
"error": "invalid_client",
"error_description": "FAPI CIBA Profile requires client authentication method to be one of: private_key_jwt, tls_client_auth, self_signed_tls_client_auth. Current method: client_secret_post"
}
invalid_request (HTTP 400)
{
"error": "invalid_request",
"error_description": "FAPI CIBA Profile requires request object lifetime to be no longer than 60 minutes. Current lifetime: 75 minutes"
}
mTLS トークンバインディング
Sender-constrained Access Tokens
FAPI CIBAでは、アクセストークンをクライアント証明書にバインドすることが必須です。