OpenID Connect CIBA(Client-Initiated Backchannel Authentication)
CIBA は、ユーザーがクライアントデバイスとは別のデバイス(認証デバイス)で認証を行う「分離型」認証フローを定義した仕様です。
目次
第1部: 概要編
CIBA とは?
CIBA(Client-Initiated Backchannel Authentication)は、従来のリダイレクトベースの認証とは異なり、バックチャネルを通じて認証を開始するフローです。
従来の認証フロー(リダイレクト):
┌────────┐ redirect ┌────────┐
│ Client │ ───────────────► │ OP │
│ │ ◄─────────────── │ │
└────────┘ redirect └────────┘
│ │
└──── 同一デバイス ────────┘
CIBA(分離型):
┌────────────────┐ backchannel ┌────────┐
│ Consumption │ ───────────────► │ OP │
│ Device │ │ │
└────────────────┘ └────────┘
│
│ push
▼
┌────────────────┐
│ Authentication │
│ Device │
│ (スマホ等) │
└────────────────┘
なぜ CIBA が必要なのか?
背景と歴史
従来の OAuth 2.0 / OpenID Connect では、認証はリダイレクトベースのフロントチャネルで行われます。この方式には以下の課題がありました:
| 課題 | 説明 |
|---|---|
| デバイス制約 | 画面やブラウザのないデバイス(POS、ATM)での認証が困難 |
| ユーザビリティ | 複数デバイスを持つユーザーが、常に同じデバイスで認証する必要がある |
| セキュリティ | 共有端末での認証時にセッション情報が残る可能性 |
| 企業向けユースケース | コールセンターオペレーターが顧客を認証できない |
CIBA は 2019 年に OpenID Foundation によって策定され、これらの課題を解決するために認証デバイスと消費デバイスを分離する設計思想を導入しました。
脅威モデル
CIBA が対処する主な脅威:
-
共有デバイスのセッションハイジャック
- 従来: 認証後のブラウザセッションが残る
- CIBA: 消費デバイスに認証情報が残らない
-
フィッシング攻撃
- binding_message で取引内容を認証デバイスに表示
- ユーザーが正当な取引であることを確認できる
-
中間者攻撃
- バックチャネル通信は TLS で保護
- クライアント認証が必須
ユースケース
| ユースケース | 説明 | 具体例 |
|---|---|---|
| コールセンター | オペレーターが顧客を認証 | 銀行のコールセンターで本人確認後、顧客のスマホに認証通知を送信 |
| POS 端末 | 店舗端末での決済認証 | レジ端末で決済時、顧客のスマホアプリで認証 |
| ATM | 銀行 ATM での本人確認 | ATM で取引開始後、スマホアプリで承認 |
| IoT デバイス | 画面のないデバイスでの認証 | スマートロックをスマホで認証して開錠 |
| スマートスピーカー | 音声デバイスでの認証 | Alexa で買い物時、スマホで承認 |
| カスタマーサポート | サポート担当が顧客を認証 | ヘルプデスクで顧客情報にアクセスする前に顧客のスマホで本人確認 |
実際のユースケースシナリオ
シナリオ1: オープンバンキングでの決済承認
1. ユーザーが EC サイトで商品を購入
2. EC サイト(RP)が銀行の決済 API を呼び出し
3. 銀行(AS)がユーザーのスマホアプリに push 通知
4. ユーザーがスマホで取引内容を確認し、承認
5. EC サイトが決済を完了
シナリオ2: コールセンターでの本人確認
1. 顧客が銀行のコ ールセンターに電話
2. オペレーターが CRM システムで顧客情報を入力
3. CRM システム(RP)が銀行の認証サーバー(AS)に CIBA リクエスト
4. 顧客のスマホに「オペレーター山田太郎が情報にアクセスします」と通知
5. 顧客がスマホで承認
6. オペレーターが顧客情報にアクセス可能に
CIBA と他の仕様との違い
| 仕様 | フロー | デバイス | 主な用途 |
|---|---|---|---|
| 標準 Authorization Code Flow | リダイレクト | 同一 | Web アプリ、モバイ ルアプリ |
| CIBA | バックチャネル | 分離 | POS、ATM、IoT、コールセンター |
| Device Flow (RFC 8628) | ポーリング | 分離 | スマートTV、CLI ツール |
CIBA と Device Flow の違い:
Device Flow:
1. デバイスがコードを表示
2. ユーザーが別デバイスでコードを入力
3. ユーザーが認証
→ ユーザーアクションが必要(コード入力)
CIBA:
1. クライアントが login_hint でユーザーを指定
2. AS が直接ユーザーの認証デバイスに通知
3. ユーザーが認証
→ ユーザーアクションが少ない(通知を承認するだけ)
3つのモード
CIBA は3つの異なる配信モードをサポートします:
| モード | 待ち時間(目安) | クライアント要件 | 本当に必要なケース |
|---|---|---|---|
| Poll | 平均30秒 (interval × 試行回数) | 公開エンドポイント不要 | ・ファイアウォール内のシステム ・NAT配下のIoTデバイス ・モバイルアプリ(直接クライアント) ・インバウンド接続不可の環境 |
| Ping | 数秒以内 | 公開HTTPSエンドポイント必須 | ・Webアプリのバックエンドサーバー ・APIサーバー ・常時稼働するサービス |
| Push | 即座 | 公開HTTPSエンドポイント必須 | ・セキュアな内部ネットワーク(VPN等) ・信頼された環境のみ (実際にはPingで十分なケースが多い) |
重要な考慮事項:
【クライアント要件の違い】
Poll:
- クライアントは公開エンドポイント不要
- インバウンド接続を受け付けなくてOK
- ファイアウォール内、NAT配下でも動作
→ モバイルアプリ、IoTデバイスに最適
Ping/Push:
- クライアントが公開HTTPSエンドポイント必須
- OPからのインバウンド接続を受け付ける
- Webサーバー機能が必要
→ バ ックエンドサーバー専用
【セキュリティの違い】
Poll/Ping:
- トークンはクライアントがToken Endpointから取得
- トークンがネットワークを流れるのは1回のみ
→ セキュア
Push:
- トークンがOPからクライアントにプッシュされる
- 通知エンドポイントの保護が重要
- TLS、通知トークン検証が必須
→ セキュリティリスク高
【モバイルアプリの実装パターン】
モバイルアプリ自体は公開HTTPSエンドポイントを持てない
→ 直接CIBAクライアントにはなれない(Ping/Push不可)
パターン1: モバイルアプリが直接クライアント(Poll)
┌──────────────┐ Poll ┌────────┐
│ モバイルアプリ │ ───────────► │ OP │
└──────────────┘ └────────┘
パターン2: バックエンド経由(Ping/Push)
┌──────────────┐ ┌────────┐
│ モバイルアプリ │ │ OP │
└──────┬───────┘ └────┬───┘
│ │
│ FCM/APNs │ Ping/Push
│ │
▼ ▼
┌──────────────────────────────────────┐
│ バックエンドサーバー │
│ (CIBAクライアント) │
└──────────────────────────────────────┘
実装:
1. バックエンドがCIBAクライアント(Ping/Poll)
2. バックエンドがFCM/APNsでモバイルに通知
3. モバイルがバックエンドからトークン取得
Poll モード:
Client OP
│ │
├──auth req─►
◄─auth_req_id
│ │
├──poll────►│ ← ユーザー認証中
◄─pending──┤
│ │
├──poll────►│
◄─pending──┤
│ │
├──poll────►│ ← 認証完了
◄─tokens───┤
│ │
Ping モード:
Client OP
│ │
├──auth req─►
◄─auth_req_id
│ │
│ │ ← ユーザー認証中
│ │
◄──ping────┤ ← 認証完了を通知
│ │
├─get token►
◄─tokens───┤
│ │
Push モード:
Client OP
│ │
├──auth req─►
◄─auth_req_id
│ │
│ │ ← ユーザー認証中
│ │
◄──tokens──┤ ← トークンを直接プッシュ
│ │
第2部: 詳細編
エンドポイント
CIBA では、通常の OIDC エンドポイントに加えて、新しいエンドポイントが導入されます。
| エンドポイント | URL 例 | 目的 |
|---|---|---|
| Backchannel Authentication Endpoint | https://auth.example.com/bc-authorize | 認証リクエストを受け付ける |
| Token Endpoint | https://auth.example.com/token | トークンを発行(既存) |
| Client Notification Endpoint | https://client.example.com/ciba/callback | RP が実装(Ping/Push) |
認証リクエスト
リクエストの構造
POST /bc-authorize HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
scope=openid profile
&client_notification_token=8d67dc78-7faa-4d41-aabd-67707b374255
&login_hint=user@example.com
&binding_message=Transaction: 100 EUR to DE89...
&requested_expiry=120
パラメータの詳細
| パラメータ | 必須 | 説明 | 推奨値 |
|---|---|---|---|
scope | ✅ | 要求するスコープ(openid 必須) | openid profile email |
client_notification_token | △ | Ping/Push モードで必須。クライアント通知エンドポイントへの認証に使用 | UUID v4 等のランダム値 |
login_hint | △ | ユーザーのヒント(メール、電話番号等) | user@example.com |
id_token_hint | △ | 既知の ID トークン。ユーザーを特定 | 以前に発行された ID Token |
login_hint_token | △ | 署名付きヒントトークン | JWT 形式 |
binding_message | △ | 認証デバイスに表示するメッセージ(最大20文字推奨) | Pay 100 EUR |
user_code | △ | ユーザーが入力するコード(追加の確認) | 123456 |
requested_expiry | △ | 認証リクエストの有効期限(秒) | 120-300 |
acr_values | △ | 要求する認証レベル | urn:example:acr:loa2 |
request | △ | 署名付き Request Object(JWT) | FAPI-CIBA では必須 |
重要な制約:
login_hint、id_token_hint、login_hint_tokenのいずれか 1 つは必須binding_messageは人間が読める平文。取引内容を明示してフィッシング対策client_notification_tokenは十分にランダムで推測不可能な値(128ビット以上推奨)
login_hint の詳細
login_hint は、ユーザーを識別するためのヒントです。AS は、このヒントを使ってユーザーの認証デバイスに通知を送ります。
login_hint の形式例:
1. メールアドレス
login_hint=user@example.com
2. 電話番号
login_hint=tel:+81-90-1234-5678
3. ユーザーID
login_hint=user_id:12345
4. カスタム形式
login_hint=custom:iban:DE89370400440532013000
AS は、login_hint の形式をサポートする必要があります。サポートする形式はディスカバリーメタデータで公開すべきです。
id_token_hint の詳細
既に取得済みの ID Token を使ってユーザーを識別します。
メリット:
- 確実にユーザーを識別できる(sub クレーム)
- AS が署名を検証できる
- セキュリティが高い
login_hint_token の詳細
署名付きの JWT でユーザーヒントを提供します。
用途:
- 第三者(信頼できる RP)がユーザーヒントを提供
- ヒントの改ざん防止
- エンタープライズシナリオ(社内システム間連携)
binding_message の詳細
binding_message は、フィッシング攻撃を防ぐための重要なパラメータです。
良い binding_message:
✅ "Pay 100 EUR to Alice"
✅ "Transfer 50000 JPY"
✅ "Approve loan #12345"
悪い binding_message:
❌ "Please authenticate" (具体性がない)
❌ "" (空)
❌ "Click here to continue" (フィッシング的)
文字数の考慮:
OpenID Connect CIBA Core 1.0仕様では、具体的な文字数制限は規定されていません。 ただし、認証デバイスでの表示を考慮した実装上の推奨があります:
デバイスごとの表示可能文字数(目安):
┌──────────────────┬──────────────┬─────────────────────┐
│ デバイス │ 1行表示 │ 複数行表示 │
├──────────────────┼──────────────┼─────────────────────┤
│ iOS通知 │ 約20-30文字 │ 約110文字(2行) │
│ Android通知 │ 約20-30文字 │ 約45-60文字(2行) │
│ スマートウォッチ │ 約10-15文字 │ 約30文字(2行) │
│ SMS │ 約30-40文字 │ 約160文字(1通) │
└──────────────────┴──────────────┴─────────────────────┘
実装上の推奨:
- 確実に1行で表示: 20文字以内
- 2行表示を許容: 50文字程度
- 詳細情報が必要: 100文字程度(ただし改行を考慮)
ベストプラクティス:
- 最も重要な情報を最初の20文字に収める
- 金額、通貨、操作種別を優先
- 送金先等の詳細は後半に配置
- 取引の具体的内容を含める
- 人間が読める形式(平文)
- 特殊文字・絵文字は避ける
例:
良い例(優先度順):
✅ "Pay 100 EUR" # 16文字(最小限)
✅ "Pay 100 EUR to Alice" # 20文字(理想的)
✅ "Transfer 50000 JPY to Account 123" # 36文字(2行表示)
悪い例:
❌ "Please authenticate for transaction" # 曖昧
❌ "重要な取引の承認をお願いします" # 具体性がない
セキュリティ上の理由:
- ユーザーが「何を承認しているか」を明確に理解
- 攻撃者が別の取引に差し替えることを防止
- RP と認証デバイスで同じ内容を表示して整合性を確保
- フィッシング攻撃の検出(不審な取引内容がすぐ分かる)
user_code の詳細
高リスク操作では、user_code を使って追加の確認を行います。
user_code の使用例:
1. RP が user_code を生成してユーザーに表示
「コード 123456 を入力してください」
2. CIBA リクエストに user_code を含める
user_code=123456
3. AS が認証デバイスで user_code の入力を要求
「端末に表示されているコードを入力してください」
4. ユーザーが 123456 を入力
5. AS が一致を確認して認証完了
メリット:
- 中間者攻撃の防止(攻撃者が user_code を知らない)
- フィッシング防止(ユーザーが取引を確認)
- 高額決済や重要操作に推奨
クライアント認証
CIBA では、Backchannel Authentication Endpoint へのリクエスト時にクライアント認証が必須です。
許可される認証方式:
✅ client_secret_basic
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
✅ client_secret_post
client_id=s6BhdRkqt3&client_secret=7Fjfp0ZBr1KtDRbnfVdmIw
✅ client_secret_jwt
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion=eyJhbGciOiJIUzI1NiIs...
✅ private_key_jwt(推奨)
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion=eyJhbGciOiJQUzI1NiIs...
✅ tls_client_auth / self_signed_tls_client_auth
mTLS で認証
FAPI-CIBA では、private_key_jwt または tls_client_auth のみ許可されます。
認証レスポンス
成功レスポンス
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"auth_req_id": "1c266114-a1be-4252-8ad1-04986c5b9ac1",
"expires_in": 120,
"interval": 5
}
| フィールド | 説明 | 推奨値 |
|---|---|---|
auth_req_id | 認証リクエスト ID。トークンリクエスト時に使用 | UUID v4 |
expires_in | 有効期限(秒) | 120-300 |
interval | ポーリング間隔(Poll モードのみ) | 5 |
auth_req_id の要件:
- ユニークであること
- 推測不可能であること(128ビット以上のエントロピー)
- 一度のみ使用可能
- 有効期限後は無効
エラーレスポンス
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "invalid_request",
"error_description": "The login_hint parameter is missing"
}
主なエラーコード:
| エラー | HTTP ステータス | 説明 | 対処法 |
|---|---|---|---|
invalid_request | 400 | リクエストパラメータが不正 | パラメータを確認 |
invalid_scope | 400 | スコープが不正 | サポートされるスコープを確認 |
expired_login_hint_token | 400 | login_hint_token が期限切れ | 新しいトークンを取得 |
unknown_user_id | 400 | ユーザーが見つからない | login_hint を確認 |
unauthorized_client | 401 | クライアント認証が失敗 | クライアント認証情報を確認 |
access_denied | 403 | AS がリクエストを拒否 | AS のポリシーを確認 |
missing_user_code | 400 | user_code が必須だが提供されていない | user_code を追加 |
invalid_user_code | 400 | user_code が不正 | user_code を確認 |
invalid_binding_message | 400 | binding_message が不正 | 文字数制限等を確認 |
Poll モード
Poll モードでは、クライアントが定期的にトークンエンドポイントをポーリングして、認証の完了を確認します。
フロー詳細
Poll モードのフロー:
┌────────┐ ┌────────┐ ┌──────────┐
│ Client │ │ OP │ │ User │
└───┬────┘ └───┬────┘ └────┬─────┘
│ │ │
│ 1. POST /bc-authorize │ │
├────────────────────────────►│ │
│ │ │
│ 2. auth_req_id, interval │ │
◄────────────────────────────┤ │
│ │ │
│ │ 3. Push notification │
│ ├──────────────────────────►│
│ │ │
│ 4. Poll (GET /token) │ │
├────────────────────────────►│ │
│ │ │
│ 5. authorization_pending │ │
◄────────────────────────────┤ │
│ │ │
│ (interval 秒待機) │ │
│ │ 4. User approves │
│ │◄──────────────────────────┤
│ │ │
│ 6. Poll (GET /token) │ │
├────────────────────────────►│ │
│ │ │
│ 7. authorization_pending │ │
◄────────────────────────────┤ │
│ │ │
│ (interval 秒待機) │ │
│ │ │
│ 8. Poll (GET /token) │ │
├────────────────────────────►│ │
│ │ │
│ 9. access_token, id_token │ │
◄────────────────────────────┤ │
│ │ │
トークンリクエスト
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
grant_type=urn:openid:params:grant-type:ciba
&auth_req_id=1c266114-a1be-4252-8ad1-04986c5b9ac1
パラメータ:
| パラメータ | 必須 | 説明 |
|---|---|---|
grant_type | ✅ | urn:openid:params:grant-type:ciba 固定 |
auth_req_id | ✅ | Backchannel Authentication Endpoint から取得した ID |
認証待ちレスポンス
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "authorization_pending",
"error_description": "The authorization request is still pending"
}
クライアントは、interval 秒待ってから再度ポーリングする必要があります。
ポーリング過多レスポンス
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "slow_down",
"error_description": "You are polling too quickly",
"interval": 10
}
slow_down エラーが返された場合:
- クライアントはポーリング間隔を増やす必要があります
- 新しい
intervalが提供されている場合はそれを使用 - 提供されていない場合は、現在の間隔に 5 秒追加
成功レスポンス
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
"id_token": "eyJhbGciOiJSUzI1NiIs..."
}
ポーリングのベストプラクティス
Ping モード
Ping モードでは、OP がクライアントに通知し、クライアントがトークンを取得します。
フロー詳細
Ping モードのフロー:
┌────────┐ ┌────────┐ ┌──────────┐
│ Client │ │ OP │ │ User │
└───┬────┘ └───┬────┘ └────┬─────┘
│ │ │
│ 1. POST /bc-authorize │ │
│ (client_notification_ │ │
│ token included) │ │
├─────────────────────── ─────►│ │
│ │ │
│ 2. auth_req_id │ │
◄────────────────────────────┤ │
│ │ │
│ │ 3. Push notification │
│ ├──────────────────────────►│
│ │ │
│ │ 4. User approves │
│ │◄──────────────────────────┤
│ │ │
│ 5. POST /callback (ping) │ │
│ Authorization: Bearer │ │
│ {client_notification_ │ │
│ token} │ │
◄────────────────────────────┤ │
│ │ │
│ 6. 200 OK │ │
├────────────────────────────►│ │
│ │ │
│ 7. POST /token │ │
├────────────────────────────►│ │
│ │ │
│ 8. access_token, id_token │ │
◄────────────────────────────┤ │
│ │ │
クライアント通知エンドポイント
Ping モードを使用する場合、クライアントはクライアント通知エンドポイントを実装する必要があります。
POST /ciba/callback HTTP/1.1
Host: client.example.com
Content-Type: application/json
Authorization: Bearer 8d67dc78-7faa-4d41-aabd-67707b374255
{
"auth_req_id": "1c266114-a1be-4252-8ad1-04986c5b9ac1"
}
リクエストボディ:
| フィールド | 説明 |
|---|---|
auth_req_id | 認証リクエスト ID |
Authorization ヘッダー:
- Bearer トークンとして
client_notification_tokenを使用 - クライアントは、この値を検証して正当な通知であることを確認
クライアント側の実装例: