ブラウザを使わないフロー: CIBA と Device Code
Device Code(RFC 8628)とCIBA(OpenID Connect Client-Initiated Backchannel Authentication 1.0)は、同じ大きな状況に対して仕様が用意している 2 つの grant です。状況とは「アクセストークンを欲しがるデバイスがまともなブラウザを持てない」こと。スマート TV、ゲーム機、CLI ツール、IoT、音声アシスタント、POS 端末、コールセンタの操作画面、ユーザの代わりに動くサーバ側プロセス、などです。
遠目に見ると 2 つのフローは同じ形に見えます — 「2 つの面が OP で合流して、ユーザはスマホで承認する」。実際には違います。両者は 誰がリクエストを起こすか と ユーザがどう OP に識別されるか で分かれており、その 1 点が他のほぼ全ての違い(通信路上のエンドポイント、ポーリングの主体、フィッシング対策の主軸、適用される規制プロファイル)を決めています。
本ページは選択ガイドです。仕様としての動き方はDevice CodeとCIBAの各ページに置いてあります。
1 段落ずつでまとめる
Device Code(RFC 8628). ブラウザを持たないデバイスが OP に「使い切りのコードをくれ」と頼み、自分の画面に表示し、ユーザに「この URL をスマホで開いて、このコードを入力して」と告げます。ユーザは手元のブラウザで認証して承認します。その間デバイスは /token をポーリングし続け、承認が降りるのを待ちます。ユーザの識別は フローの途中で発覚 します — TV の前に誰がやって来るかは OP もデバイスも事前には知りません。
CIBA(Core 1.0). RP はすでにユーザが誰かを知っています — login_hint([email protected]、口座番号など)、id_token_hint(過去に発行された ID トークン)、login_hint_token(上流のシステムが発行した署名付き JWT)のいずれかで。RP は OP に「このユーザを別経路で認証してくれ」と頼みます。組み込み側の通知・認証デバイスサービスがユーザへ通知し、OP はリクエストを保持して RP のポーリングに応答します。本ライブラリは poll 方式だけを実装し、ping / push 配信方式は提供しません。ユーザの識別は RP が事前に与えるもの なので、配備側は承認先の認証デバイスを決められます。
比較
| 観点 | Device Code(RFC 8628) | CIBA(Core 1.0) |
|---|---|---|
| 発端 | ブラウザを持たないデバイス(入力デバイス → OP) | RP / API クライアント(RP → OP) |
| ユーザの識別 | ユーザが別のブラウザに user_code を入力 | RP が login_hint / id_token_hint / login_hint_token を事前に渡す |
| ユーザ側の端末 | ユーザが手元に持っている任意のブラウザ | 配備側が事前登録した別経路承認用デバイス |
| フィッシング対策の主軸 | デバイスが表示する user_code + ユーザが検証 URI(verification URI)のホストを目視 | ユーザの認証デバイスに表示する binding_message |
| ブラウザの関与 | あり(ユーザのスマホ側) | 任意 / 不要(組み込み側の認証デバイスが確認する) |
| ポーリング主体 | ブラウザを持たないデバイス | RP |
| 仕様上のエンドポイント | /device_authorization | /bc-authorize |
| token grant_type | urn:ietf:params:oauth:grant-type:device_code | urn:openid:params:grant-type:ciba |
| 主な用途 | TV アプリ、CLI ツール、kiosk、音声アシスタント、入力手段が乏しい IoT | 強力な顧客認証(PSD2 風)、金融 / ヘルスケアでの別経路承認、画面共有なしでアクセスをリセットするカスタマーサポートのフロー |
| 本ライブラリでの対応 | RFC 8628 — op.WithDeviceCodeGrant で有効化 | OIDC CIBA Core 1.0 — poll モードのみ。Discovery も poll だけを広告し、ping / push は実装しない |
| ブルートフォース対策 | op/devicecodekit の opaque な attempt-key リミッタ + レコード単位ロックアウト(MaxUserCodeStrikes、5 回)、さらに slow_down 違反 5 回で poll-abuse ロックアウト | poll-abuse ロックアウト — auth_req_id ごとに slow_down 違反 5 回(CIBAGrant.MaxPollViolations)、閾値超過で audit.CIBAPollAbuseLockout。ユーザが打つコードがないので推測の対象もない |
| FAPI プロファイルとの関係 | Device Code は FAPI 2.0 Security Profile で分析済みの認可フローには含まれません | FAPI-CIBA は FAPI 2.0 Security Profile / Message Signing とは別のプロファイルです。本ライブラリの CIBA 設定は JAR、DPoP または mTLS、アクセストークンの有効期間と auth_req_id の有効期間の上限 10 分を要求します |
2 つの grant、同じ症状
両方の grant が存在するのは、正攻法の authorization_code + PKCE フローがそもそも「token を欲しがるデバイスにまともなブラウザがある」前提で組まれているからです。その前提は TV、CLI、IoT、音声アシスタント、ユーザの代わりに動くバックエンドサービス、では成り立ちません。RFC 8628 と CIBA は「ブラウザがどこか別の場所にある」という状況の、異なる 2 つの形 を解いています。
どちらを選ぶか — 判定木
順番に 4 つの問いを通ってください。最初の問いでだいたい片付きます。
1. RP はフロー開始 前 にユーザが誰かを知っているか?
- はい → CIBA。RP はすでに
login_hint(または ID トークン、hint 用トークン)を持っており、/bc-authorizeに乗せて送れます。ユーザは自分を識別するための入力をしません。 - いいえ → Device Code。ユーザは verification ページでサインインすることでフローの中で自分を識別し、OP はそこでユーザが誰かを知ります。
2. token を欲しがるデバイスに画面はあるか?
- ある → Device Code がそのまま
user_codeとverification_uriを表示できます。TV / ゲーム機 / CLI の典型ケース。 - ない(音声アシスタント、ヘッドレス IoT 等) → どちらでも動きます。Device Code は TTS で
user_codeを読み上げる、verification_uri_completeを QR で出す等の方法が取れます。CIBA は別の認証デバイスを使い、組み込み側の通知サービスが承認 prompt を届けるため、利用デバイス自体の表示は不要です。
3. ユーザは認証デバイスを事前登録しているか?
- CIBA は前提とします。 事前登録された認証デバイスがないと、組み込み側の承認サービスは届け先を持ちません。そのデバイスを事前登録する工程(銀行アプリ、スタッフのスマホ、規制当局が発行する authenticator、等)は配備の一部です。
- Device Code は前提としません。 ユーザがサインインできる任意のブラウザセッションで動きます。本人のスマホ、同僚のラップトップ、店舗の kiosk、いずれでも。
4. これは規制下の金融 / ヘルスケアで、別経路承認が要件になっているか?
- CIBA はこの用途のために設計されています。 旧 FAPI-CIBA-ID1 は FAPI 1.0 と mTLS を基礎としています。本ライブラリの
profile.FAPICIBAは JAR を要求し、送信者制約として DPoP または mTLS を認め、アクセストークンの有効期間を 10 分以内に制限します。この設定だけで ID1 への適合は示せません。binding_messageは、ユーザが承認するリクエストを識別する助けになります。 - Device Code は汎用的な仕組みです。 適切に組めば十分機能しますが、規制当局が SCA で名指ししてくる形にはなっていません。
判定木を抜けてもまだ迷うなら、既定としては「画面はあるがブラウザがない」一般消費者向けには Device Code、「RP がユーザを把握済みで、別経路で承認だけしてほしい」業務向けには CIBA を選んでください。
シーケンス図
Device Code(RFC 8628)
user_code だけで、それを運ぶのは片方の画面を読んでもう片方に入力する人間です。CIBA(Core 1.0、poll モード)
形は頭の中でマッピングできる程度には似ています。決定的な違いは斜めに伸びる矢印です。Device Code ではユーザが コードを手に OP まで歩いてくる のに対し、CIBA では OP が ユーザがすでに信頼しているデバイスに手を伸ばす。
脅威モデルの並列比較
Phishing — 攻撃者がユーザを騙して 攻撃者の リクエストを承認させる.
- Device Code: ユーザは入力する URL のホストを目視で確認します。
user_code自体に session 単位の秘密値はありません(エントロピは意図的に控えめ)。ユーザがホストを打ち間違えたり phishing メールのリンクを踏んだりすれば、同じuser_codeが攻撃者のサイトでも通ってしまいます。 - CIBA:
binding_messageが/bc-authorizeに同梱され、ユーザの認証デバイスに表示されます。ユーザは「Acme POS 端末 #14: Acme Coffee で 800 円を承認?」を見てから承認します。文脈のない別経路確認画面(「異常なアクティビティを検知しました、承認しますか?」)が失敗モードです。
user_code への replay / brute-force.
- Device Code:
user_codeはユーザが手で入力できるよう短く(BDWP-HQPK等)作られています。原理的には総当たり可能です。本ライブラリはop/devicecodekitを同梱しており、最初の手入力は生のuser_codeではない opaque なサーバ側 ceremony key とVerifyUserCodeByAttemptKeyで検証し、その後ApproveUserCodeまたはDenyUserCodeを呼びます。レコード単位のヘルパはMaxUserCodeStrikes(既定 5)も適用します。組み込み側が用意する verification ページは 必ず このヘルパを経由してください。 - CIBA: ユーザが手で打つコードはありません。
auth_req_idは不透明値で OP が発行します。
/token ポーリングへの replay / abuse.
- 両フローとも、ユーザが判断中は
authorization_pendingを、intervalより速くポーリングしてきたらslow_downを返します。RFC 8628 §3.5 は新しいintervalの遵守を必須としており、OP はLastPolledAtと原子的に値を永続化するため、複数レプリカ構成で値をリセットされる経路はありません。 - 両フローとも、
slow_down違反が 5 回に達するとレコードをロックします。リクエストはreason="poll_abuse"で拒否され、次のポーリングはaccess_deniedになります。CIBA では加えてaudit.CIBAPollAbuseLockoutが記録され、CIBA の閾値はCIBAGrant.MaxPollViolationsで調整できます。
本ライブラリの現状の対応
Device Code(RFC 8628). フル対応で、op.WithDeviceCodeGrant で有効化します(このオプションが grant を有効な集合に加えるので、op.WithGrants に grant.DeviceCode を併記しても何も変わりません)。verification ページ(ユーザが user_code を入力する画面)は 組み込み側がホスト します。最初の手入力は生の user_code ではない opaque なサーバ側 ceremony key と devicecodekit.VerifyUserCodeByAttemptKey で検証し、その後 ApproveUserCode / DenyUserCode を呼びます。devicecodekit.Deps はポインタのまま保持・受け渡しし、既定のリミッタはプロセス内限定です。複数インスタンスでは共有のアトミックな AttemptLimiter を注入します。監査は Deps.AuditLogger 経由です。監査イベントのカタログ:
audit.DeviceAuthorizationIssued、audit.DeviceAuthorizationRejected、audit.DeviceAuthorizationUnboundRejectedaudit.DeviceCodeVerificationApproved、audit.DeviceCodeVerificationDenied、audit.DeviceCodeUserCodeBruteForceaudit.DeviceCodeTokenIssued、audit.DeviceCodeTokenRejected、audit.DeviceCodeTokenSlowDownaudit.DeviceCodePollObservationFailed(token endpoint がきれいに反映できない状態遷移を観測したとき)audit.DeviceCodeRevoked(公開Revokeヘルパから発火)
CIBA(Core 1.0). poll モードのみで、Discovery も poll だけを広告します。ping / push 配信方式は提供しません。op.WithCIBAGrant(op.CIBAGrant{HintResolver: ...}) で有効化します。リゾルバは必須です。HintResolver は組み込み側のフックで、受信した hint(login_hint、id_token_hint、login_hint_token)を subject に解決します。id_token_hint は OP が署名・issuer・認証済みクライアント audience を検証し、検証済み sub だけをリゾルバに渡します。exp は意図的に検証せず、typ が JWT でないトークンは拒否し、pairwise クライアントはリゾルバの前に拒否します。監査イベントのカタログ:
audit.CIBAAuthorizationIssued、audit.CIBAAuthorizationRejected、audit.CIBAAuthorizationUnboundRejectedaudit.CIBAPollAbuseLockoutaudit.CIBATokenIssued、audit.CIBATokenRejected、audit.CIBATokenSlowDownaudit.CIBAPollObservationFailed(token endpoint がきれいに反映できない状態遷移を観測したとき)
認証デバイスの callback は組み込み側が CIBA サブストアを直接呼ぶ経路です。その呼び出しに対して本ライブラリは audit.CIBAAuthDeviceApproved / audit.CIBAAuthDeviceDenied を発火しません。これらの名前は配備側のテレメトリ用のカタログです。
続きはこちら
- Device Code 入門 — RFC 8628 の動き方、ポーリング応答、
user_codeに対する総当たりの防御。 - CIBA 入門 — CIBA Core 1.0 の動き方、hint の種類、
binding_message、FAPI-CIBA プロファイル。 - ユースケース: Device Code の組み込み —
op.WithDeviceCodeGrant、verification ページの契約、連鎖失効。 - ユースケース: CIBA の組み込み —
op.WithCIBAGrant、HintResolverの契約、FAPI-CIBA の制約。 - 監査イベントカタログ — 中身の形式付きの全カタログ。