CIBA — Client-Initiated Backchannel Authentication
CIBA は Device Code とは別の形の問題を解きます。device code は 2 つの面が「画面に表示された短いコード」を介して OP で合流します。CIBA ではリクエストを開始する装置がそもそも ユーザに見えていない — 組み込み側の通知サービスが、ユーザがすでに信頼している 別の認証デバイス に要求を届けます。
代表的な構成:
- 利用デバイス(consumption device) — POS 端末、コールセンタの操作画面、店内のキオスク端末、銀行の振込承認パネル等。誰 であるべきかは知っている(ロイヤリティカード、電話番号、口座番号)が、認証する手段はない。
- 認証デバイス(authentication device) — ユーザのスマホで、銀行アプリがすでにインストール・サインイン済み。組み込み側の通知サービスが「Acme Coffee で 800 円を承認しますか?」などの要求を表示し、ユーザは 承認 / 拒否 をタップする。
利用デバイスはユーザに credential を一切聞きません — OP に「Alice にスマホで承認してもらってください」と頼むだけです。
このページで触れる仕様
- OpenID Connect Client-Initiated Backchannel Authentication Flow — Core 1.0 — CIBA Core 仕様
- FAPI-CIBA-ID1 — FAPI 1.0 を基礎とし、mTLS 時代の送信者制約を使う旧プロファイル。本ライブラリの
profile.FAPICIBAは DPoP も認めるため、設定だけで ID1 への適合は示せません。
用語の補足
auth_req_id—/bc-authorizeの応答として返る不透明な識別子。利用デバイスはこれを使って/tokenをポーリングし、認証デバイスはこれに対して承認する。- Hint — 利用デバイスが OP に どのユーザ を聞くかを伝える方法。CIBA Core §7.1 で 3 種類が定義されている:
login_hint— 組み込み側が subject にマップする不透明値([email protected]、口座番号など)。id_token_hint— 過去に発行された ID トークン。subclaim でユーザを識別する。login_hint_token— 組み込み側が署名検証してから subject にマップする署名付き JWT(別の上流システムが発行したものなど)。
- 配信モード — OP が承認を利用デバイスに伝える方法:
- poll — デバイスが
auth_req_idで/tokenをポーリングします。本ライブラリが実装している配信モードはこれだけです。
- poll — デバイスが
フローの動き方(poll モード)
binding_message が重要なのはそのためで、端末の前にいる人とスマホを持つ人が突き合わせられる唯一の文字列がこれです。利用デバイスはユーザの credential を持ちません。ユーザは利用デバイスに何も打ちません。認証デバイス — ユーザが銀行アプリにサインイン済みなのですでに認証されている — だけが consent を行使する場所になります。
CIBA と Device Code — どちらを選ぶか
両方とも 2 デバイスです。違いは 「利用デバイスが聞いていることをユーザが知っているか」 です。
| Device Code | CIBA | |
|---|---|---|
| 誰が信頼を起こす? | ユーザが認証ページに user_code を打ち込む | 利用デバイスが OP に要求し、組み込み側の認証デバイスサービスが表示する |
| ユーザは URL を発見する必要がある? | はい — verification_uri が画面に出る | いいえ — 配備側が認証デバイスをあらかじめ把握している |
| 利用デバイス側の信頼モデル | 匿名のデバイスがユーザに「結びつけて」とお願いする | 事前登録されたデバイスが、OP 経由でユーザに「確認して」とお願いする |
| 代表的な利用面 | スマート TV、ゲーム機、CLI、IoT 機器のペアリング | POS、コールセンタ、不正検知の確認、アプリ内決済 |
| ユーザが打つ識別子 | user_code(BDWP-HQPK 等) | なし — OP はすでにユーザ識別子を持っている(login_hint) |
| 誤誘導のリスク | 低 — URL がデバイス画面に出ている | 中 — 別経路の通知文言が利用面と一致しているとユーザが信頼する必要がある。binding_message を使ってスマホ側プロンプトに POS の要求内容を表示すること |
ユーザが デバイスの目の前にいて デバイスがコード表示できないなら CIBA。ユーザが デバイスから離れていて デバイスが画面を持っているなら device code。CIBA の認証デバイスは事前にユーザを知っている必要があり、device code の verification ページは任意のサインイン済みブラウザセッションで動きます。
Hint — 「どのユーザか」を OP に伝える
利用デバイスはユーザを認証できないので、OP に どのユーザを承認サービスの対象にするか を伝える必要があります。CIBA Core §7.1 は 3 種類の hint を定義しており、OP は単一の HintResolver インタフェースでそれら全てを受け付けます:
op.WithCIBAGrant(op.CIBAGrant{
HintResolver: op.HintResolverFunc(
func(ctx context.Context, kind op.HintKind, value string) (string, error) {
switch kind {
case op.HintLoginHint:
// value = "alice"、"[email protected]"、口座番号など。
return resolveLoginHint(ctx, value)
case op.HintIDTokenHint:
// value は raw JWT ではなく、OP が検証した subject です。
// OP は署名、issuer、認証済みクライアントの audience を検証しますが、
// CIBA hint では exp を意図的に検証しません。
return lookupSubject(ctx, value)
case op.HintLoginHintToken:
// value = 信頼している別システムが発行した署名付き JWT。
return verifyAndMap(ctx, value)
}
return "", op.ErrUnknownCIBAUser
},
),
})op.ErrUnknownCIBAUser を返すと、通信路上の応答は unknown_user_id に丸められます。それ以外のエラーは login_required になります。op.WithCIBAGrant を HintResolver 未指定で呼ぶと op.New は構築に失敗します — リゾルバは必須です。
HintIDTokenHint では、OP が自分の鍵、issuer、認証済みクライアントの audience に対して JWT を検証し、検証済みの sub だけを Resolve に渡します。exp は意図的に検証しません。typ ヘッダが無い、または JWT 以外のトークンは拒否するので、logout token やアクセストークンを hint として通すことはできません。pairwise subject を登録したクライアントは、sector ごとの sub を元の subject に戻せないため Resolve の前に拒否されます。そのクライアントは login_hint または login_hint_token を使ってください。resolver で raw JWT を parse してはなりません。
binding_message — 誤誘導を防ぐ項目
CIBA の binding_message は利用デバイスが /bc-authorize 時に送れる短い文字列です。OP はこれを認証デバイスに転送し、ユーザのスマホ側プロンプトにレジ係が POS で見ているのと同じ文言を表示できます:
Acme POS 端末 #14: Acme Coffee で 800 円を承認しますか?
[ 承認 ] [ 拒否 ]
binding_message がないと、ユーザは OP の汎用プロンプトしか頼りになりません。「異常なアクティビティを検知しました。この通知を承認してください」のような phishing がはるかに通りやすくなります。仕様上は optional ですが、組み込み側の UX では 必須 として扱ってください。
OP が受け付けるのは 50 文字までで、制御文字は拒否します(400 invalid_binding_message)。文字列は受け取ったとおりに転送し、HTML エスケープはしません。両方のデバイスで同じ文言を表示できるようにするためです。表示先に合わせたエスケープは組み込み側で行ってください。
各エンドポイントの応答
/bc-authorize はリクエストの時点で次のように拒否します。
| 条件 | 応答 |
|---|---|
hint が解決できない(op.ErrUnknownCIBAUser) | 400 unknown_user_id |
| リゾルバがそれ以外のエラーを返した | login_required |
pairwise クライアントからの id_token_hint | 400 invalid_request |
binding_message が長すぎる、または制御文字を含む | 400 invalid_binding_message |
user_code または client_notification_token が付いている | 400 invalid_request(どちらも未対応) |
requested_expiry が正の整数でない、または profile.FAPICIBA 下で 10 分を超える | 400 invalid_request(FAPI-CIBA 以外で CIBAGrant.MaxRequestTTL を超える値は切り詰められる) |
| クライアントの JWKS を取得できない request object | 503 |
grant_type=urn:openid:params:grant-type:ciba の /token は、Device Code と同じ 4 つのポーリング用コードで応答します。authorization_pending、slow_down、access_denied(認証デバイスで拒否された、または slow_down 違反が 5 回)、expired_token です。
auth_req_id の有効期間は既定で 600 秒(requested_expiry で短縮可能)、interval は 5 秒です。op.CIBAGrant の DefaultRequestTTL、MaxRequestTTL、PollInterval フィールドで調整できます。負の値、および profile.FAPICIBA 下での 10 分を超える有効期間は、op.New が失敗します。
承認されたリクエストを引き換える時点で、OP は Device Code と同様にそのクライアントへの同意の grant を記録します。introspection と /userinfo は公開(pairwise)subject を返し、subject の失効でトークンも失効し、同じ scope での後続の /auth では同意画面が省略されます。
動かしてみる
examples/32-ciba-pos は完全な POS シナリオを実演します。POS が /bc-authorize に POST、スタッフのスマホ(CIBARequestStore.Approve を直接呼ぶ goroutine でシミュレート)が承認、POS がトークン発行までポーリングします。end-to-end で 5 秒程度です。
(cd examples/32-ciba-pos && GOWORK=off go run -tags example .)example はロール別ファイルに分割されています(op.go で OP の組み立て + HintResolver、rp.go で POS 側のポーリング、device.go でスマホ承認のシミュレーション)。
続きはこちら
- ユースケース: CIBA の組み込み —
op.WithCIBAGrant、HintResolverの契約、FAPI-CIBA プロファイル制約、組み込み側の認証デバイスコールバックがCIBARequestStore.Approveに応答する手順。 - 認可コード + PKCE — ユーザがクライアントの目の前にいるときに使うフロー。
- Device Code 入門 — 「ユーザが別の利用面にいる」という同系統の概念。コード表示を使う方式。