実装者による CIBA 解説

# はじめに

この記事では、『OpenID Connect Client Initiated Backchannel Authentication Flow - Core 1.0』、通称『**CIBA Core**』の解説をおこなう。（CIBA は『シーバ』と読む）

* [OpenID Connect Client Initiated Backchannel Authentication Flow - Core 1.0](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html)

CIBA Core は 2018 年 12 月 14 日に Public Review 期間に入った（[アナウンス](https://openid.net/2018/12/14/public-review-period-for-openid-connect-client-initiated-backchannel-authentication-ciba-core-started/)）。スケジュール通りに進めば、2019 年 2 月初旬には Implementer's Draft として承認される<sup>※</sup>。

<sup>※：2019 年 2 月 4 日に承認の[アナウンス](https://openid.net/2019/02/04/implementers-draft-of-openid-connect-client-initiated-backchannel-authentication-ciba-core-approved/)があった。</sup>

Public Review に先立ち、実装者の視点からいろいろ突っ込みを入れておいたので、実装上大きく問題になる箇所はないと思われる<sub>（突っ込みを入れ過ぎた結果、仕様書の Acknowledgements に私の名前が記載されている）</sub>。また、実際に Authlete 本体に CIBA の実装を追加することもできた。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Authlete 本体の CIBA 実装終了。全てのトークンデリバリーモード（POLL/PING/PUSH）に対応。現在 Public Review 中の CIBA の商用実装を年内に仕上げ終わるの、世界で Authlete 社だけとなるだろう。強調しとくと、これは、日本のスタートアップがこの分野で世界一を突っ走ってるって意味だからね。</p>&mdash; Takahiko Kawasaki (@darutk) <a href="https://twitter.com/darutk/status/1079100484462374912?ref_src=twsrc%5Etfw">2018年12月29日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

|![ciba-merge-request.png](https://qiita-image-store.s3.amazonaws.com/0/106044/eee7db81-6478-edb5-c3d3-9a198b258c7a.png)|
|:-:|
|feature/ciba ブランチから Authlete 2.1 へのマージリクエスト|

以下、仕様策定作業に参加していた経験と、実際に実装した経験をもとに、CIBA について解説する。


# 1. CIBA 概要

## 1.1. フロー開始方法

[RFC 6749](https://tools.ietf.org/html/rfc6749) で定義されている旧来の認可コードフローおよびインプリシットフローでは、クライアントアプリケーションが Web ブラウザを介して認可サーバーの認可エンドポイントに認可リクエストを投げるところからフローが始まる（参考→『[OAuth 2.0 の認可レスポンスとリダイレクトに関する説明](https://qiita.com/TakahikoKawasaki/items/8567c80528da43c7e844)』）。

<img width="677" alt="start-from-authorization-endpoint.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/73d22d3c-8ca0-1bcf-41cf-f607104e8d0f.png">

一方 CIBA では、**バックチャネル認証エンドポイント**（Backchannel Authentication Endpoint）という新たに定義されたエンドポイントに対してクライアントアプリケーションが直接**バックチャネル認証リクエスト**（Backchannel Authentication Request）を投げるところからフローが始まる。

<img width="677" alt="start-from-backchannel-authentication-endpoint.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/18fbf3a1-d860-8187-2e57-ef915a625494.png">


## 1.2. ユーザー認証・同意取得

旧来のフローと CIBA フロー、どちらの場合も、リクエストを受けた認可サーバーは、クライアントアプリケーションが承認を求めていることをユーザーに伝え、承認するか否かの判断をユーザーに仰ぐ。旧来のフローでは、この処理を、認可画面を表す HTML を認可エンドポイントから Web ブラウザに送り返すことによって実現する。認可サーバーは、ユーザーの判断結果を、HTML フォームが生成する HTTP リクエストとして受け取る。

<img width="677" alt="user-consent-via-web-browser.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/094d4204-7e73-8ce9-75b9-cce5e46468b9.png">

一方 CIBA では、ユーザー認証処理および同意取得処理を、ユーザーの**認証デバイス**（Authentication Device）に担ってもらう。認証デバイスの典型例はスマートフォンだ。この処理は、**バックチャネル認証エンドポイントからクライアントに対して応答を返したあと、バックグラウンドで行われる**。

<img width="676" alt="user-consent-via-authentication-device.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/c24c1c0e-4350-4754-8b89-f974d6f0b797.png">

CIBA のユーザー認証・同意取得において注目すべきは、**クライアントアプリケーションがユーザーの管理下になく**、また、**クライアントアプリケーションと認証デバイスが物理的に離れていても問題無い**点だ。例えば、クライアントアプリケーションが沖縄のコールセンターのオペレーターの目の前で動いているアプリケーションだとしても、ユーザー認証・同意取得処理は、東京からコールセンターに電話をかけているユーザーの手元にあるスマートフォン上でおこなわれる、といったことが CIBA により実現可能となる。


## 1.3. 同意後のトークン発行

旧来のフローでは、ユーザーの同意を得たあと、認可サーバーは、認可コードフローでは認可コードを発行し、インプリシットフローではアクセストークンを直接発行する。いずれにしても、認可サーバーはクライアントアプリケーションと直接やりとりできないので、Web ブラウザを介してクライアントアプリケーションに認可コードもしくはアクセストークンを渡さなければならない。このときに使われるトリックが HTTP リダイレクトで、図にすると次のようになる（詳細解説→『[OAuth 2.0 の認可レスポンスとリダイレクトに関する説明](https://qiita.com/TakahikoKawasaki/items/8567c80528da43c7e844)』）。旧来のフローが『Redirect フロー』に分類されるのはこれが理由である。

|![redirection-in-authorization-code-flow.png](https://qiita-image-store.s3.amazonaws.com/0/106044/4810d5b6-b4f5-631f-ea40-d4034d012d0b.png)|
|:-:|
|認可コードフローのリダイレクト|

|![redirection-in-implicit-flow.png](https://qiita-image-store.s3.amazonaws.com/0/106044/33c40c5e-5152-5276-099f-74bd2b4fff73.png)|
|:-:|
|インプリシットフローのリダイレクト|

一方 CIBA では、ユーザーの同意を得たあとの処理の流れは、三種類ある。それぞれ、**POLL モード**、**PING モード**、**PUSH モード**と呼ばれる。いずれも、最終的にはクライアントに ID トークン・アクセストークン・リフレッシュトークン（任意）を発行することになる。


## 1.4. POLL モード

POLL モードでは、バックチャネル認証エンドポイントにリクエストを投げて応答を得たあと、**クライアントはトークンエンドポイントに対してトークンリクエストを繰り返す**（ポーリング）。

<img width="695" alt="ciba-poll-mode.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/32b3d144-d773-6c62-c61c-f75735401b20.png">

トークンリクエストを受けたときにまだ認証デバイスでの処理が終わっていない場合、認可サーバーは `"error":"authorization_pending"` を含む JSON を `400 Bad Request` で返す。クライアントは、このエラーを受け取っている間は、トークンリクエストを繰り返す。なお、トークンリクエストの間隔が短過ぎると、認可サーバーは `"error":"slow_down"` を返してくる。

ID トークン・アクセストークン・リフレッシュトークン（任意）を受け取るか、もしくは、`authorization_pending` / `slow_down` 以外のエラーを受け取れば、クライアントはトークンエンドポイントへのポーリングをやめる。


## 1.5. PING モード

PING モードでは、認証デバイスでのユーザー認証・同意取得処理が終わったあと、認可サーバーはクライアントの**クライアント通知エンドポイント**（Client Notification Endpoint）に対して通知を送る。この**通知を受けたあと、クライアントはトークンエンドポイントにトークンリクエストを投げる**。

<img width="696" alt="ciba-ping-mode.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/467a22ca-c59f-e7a0-a937-a7eda259e9d5.png">

クライアント通知エンドポイントとクライアントアプリケーション本体は別々でかまわないが、クライアント通知エンドポイントの実装は、通知を受け取ったことをクライアントアプリケーション本体に何らかの方法で伝えることができなければならない。

トークンエンドポイントからは、ID トークン・アクセストークン・リフレッシュトークン（任意）が返ってくる。


## 1.6. PUSH モード

PUSH モードでは、認証デバイスでのユーザー認証・同意取得処理が終わったあと、認可サーバーは **ID トークン・アクセストークン・リフレッシュトークン（任意）を生成し、それらをクライアント通知エンドポイントに直接渡す**。

<img width="696" alt="ciba-push-mode.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/bd4ad3c4-d9d8-244d-43df-f8d5d0ac390e.png">

PING モードと同様、クライアント通知エンドポイントとクライアントアプリケーション本体は別々でかまわないが、クライアント通知エンドポイントの実装は、受け取ったトークン群をクライアントアプリケーション本体に何らかの方法で渡すことができなければならない。


# 2. CIBA リクエスト・レスポンスまとめ

ここでは、CIBA フローのリクエスト・レスポンスのパラメーター群について簡潔にまとめる。各パラメーターの詳細については[仕様書原文](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html)を参照のこと。


## 2.1. 共通

### 2.1.1. クライアント認証

CIBA フロー内のバックチャネル認証リクエストおよびトークンリクエストにおいて、クライアント認証方式により、次のパラメーター群が追加で必要となる。

| クライアント認証方式 | 追加パラメーター等 |
|:--|:--|
|`client_secret_basic`        |`Authorization` ヘッダー|
|`client_secret_post`         |`client_id`, `client_secret`|
|`client_secret_jwt`          |`client_assertion`, `client_assertion_type`|
|`private_key_jwt`            |`client_assertion`, `client_assertion_type`|
|`tls_client_auth`            |`client_id`, クライアント証明書|
|`self_signed_tls_client_auth`|`client_id`, クライアント証明書|


### 2.1.2. `request` リクエストパラメーター

バックチャネル認証リクエストでは、`request` という名の特別なリクエストパラメーターを使うことができる。詳細については、後述の「3.1. リクエストオブジェクト」を参照のこと。


### 2.1.3. トークンリクエスト

POLL モードと PING モードでは、クライアントはトークンリクエストをおこなう。リクエストの内容は両モードで共通となる。

```http
POST {トークンエンドポイント} HTTP/1.1
Host: {認可サーバー}
Content-Type: application/x-www-form-urlencoded

grant_type={グラントタイプ}&                                // 必須。
auth_req_id={バックチャネル認証エンドポイントから発行されたもの}  // 必須。
```

* `grant_type` の値は `urn:openid:params:grant-type:ciba` で固定。


### 2.1.4. トークンレスポンス

POLL モードと PING モードでは、クライアントはトークンリクエストをおこなう。それに応じてトークンエンドポイントはレスポンスを返すが、その内容は両モードで共通となる。

```http
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "access_token": "{アクセストークン}",
  "token_type": "{トークンタイプ}",
  "refresh_token": "{リフレッシュトークン}",  // 任意
  "expires_in": {アクセストークン有効期間秒数},
  "id_token": "{IDトークン}"
}
```


## 2.2. POLL モード

<img width="695" alt="ciba-poll-mode.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/32b3d144-d773-6c62-c61c-f75735401b20.png">
（再掲）


### 2.2.1. POLL モード：バックチャネル認証リクエスト

```http
POST {バックチャネル認証エンドポイント} HTTP/1.1
Host: {認可サーバー}
Content-Type: application/x-www-form-urlencoded

scope={スコープ群}&                                    // 必須。openid を含める。
acr_values={認証コンテキストクラス群}&                   // 任意。
(login_hint_token|id_token_hint|login_hint)={ヒント}& // いずれかが必須。
binding_message={メッセージ}&                         // 任意。
user_code={ユーザーコード}&                            // 設定によっては必須。
requested_expiry={auth_req_id有効秒数}                // 任意。
```

* POLL モードでは通知が行われないので、バックチャネル認証リクエストに `client_notification_token` を含める必要はない。


### 2.2.2. POLL モード：バックチャネル認証レスポンス

```http
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "auth_req_id": "{新規発行されたauth_req_id}",
  "expires_in": {新規発行されたauth_req_idの有効期間秒数},
  "interval": {最小許容ポーリング間隔秒数}
}
```


## 2.3. PING モード

<img width="696" alt="ciba-ping-mode.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/467a22ca-c59f-e7a0-a937-a7eda259e9d5.png">
（再掲）


### 2.3.1. PING モード：バックチャネル認証リクエスト

```http
POST {バックチャネル認証エンドポイント} HTTP/1.1
Host: {認可サーバー}
Content-Type: application/x-www-form-urlencoded

scope={スコープ群}&                                    // 必須。openid を含める。
client_notification_token={クライアント通知トークン}&    // 必須。
acr_values={認証コンテキストクラス群}&                   // 任意。
(login_hint_token|id_token_hint|login_hint)={ヒント}& // いずれかが必須。
binding_message={メッセージ}&                         // 任意。
user_code={ユーザーコード}&                            // 設定によっては必須。
requested_expiry={auth_req_id有効秒数}                // 任意。
```

* PING モードでは通知が行われるので、`client_notification_token` は必須となる。


### 2.3.2. PING モード：バックチャネル認証レスポンス

```http
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "auth_req_id": "{新規発行されたauth_req_id}",
  "expires_in": {新規発行されたauth_req_idの有効期間秒数},
  "interval": {最小許容ポーリング間隔秒数}
}
```


### 2.3.3. PING モード：通知

```http
POST {クライアント通知エンドポイント} HTTP/1.1
Host: {通知を受けるサーバー}
Authorization: Bearer {クライアント通知トークン}
Content-Type: application/json

{
  "auth_req_id": "{バックチャネル認証エンドポイントから発行された値と同じ}"
}
```


## 2.4. PUSH モード

<img width="696" alt="ciba-push-mode.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/bd4ad3c4-d9d8-244d-43df-f8d5d0ac390e.png">
（再掲）

### 2.4.1. PUSH モード：バックチャネル認証リクエスト

```http
POST {バックチャネル認証エンドポイント} HTTP/1.1
Host: {認可サーバー}
Content-Type: application/x-www-form-urlencoded

scope={スコープ群}&                                    // 必須。openid を含める。
client_notification_token={クライアント通知トークン}&    // 必須。
acr_values={認証コンテキストクラス群}&                   // 任意。
(login_hint_token|id_token_hint|login_hint)={ヒント}& // いずれかが必須。
binding_message={メッセージ}&                         // 任意。
user_code={ユーザーコード}&                            // 設定によっては必須。
requested_expiry={auth_req_id有効秒数}                // 任意。
```

* PUSH モードでは通知が行われるので、`client_notification_token` は必須となる。


### 2.4.2. PUSH モード：バックチャネル認証レスポンス

```http
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "auth_req_id": "{新規発行されたauth_req_id}",
  "expires_in": {新規発行されたauth_req_idの有効期間秒数}
}
```

* PUSH モードではトークンリクエストは行われないので、バックチャネル認証レスポンスに `interval` は含まれない。


### 2.4.3. PUSH モード：通知

```http
POST {クライアント通知エンドポイント} HTTP/1.1
Host: {通知を受けるサーバー}
Authorization: Bearer {クライアント通知トークン}
Content-Type: application/json

{
  "auth_req_id": "{バックチャネル認証エンドポイントから発行された値と同じ}",
  "access_token": "{アクセストークン}",
  "token_type": "{トークンタイプ}",
  "refresh_token": "{リフレッシュトークン}",  // 任意
  "expires_in": {アクセストークン有効期間秒数},
  "id_token": "{IDトークン}"
}
```

* PUSH モードの通知に含まれる ID トークンには、`auth_req_id` の値を値として持つ`urn:openid:params:jwt:claim:auth_req_id` クレームが含まれる。また、リフレッシュトークンが発行されている場合には、そのハッシュ値を値として持つ `urn:openid:params:jwt:claim:rt_hash` クレームが含まれる。リフレッシュトークンのハッシュ値の計算方法は、『OIDC Core [3.2.2.9. Access Token Validation](https://openid.net/specs/openid-connect-core-1_0.html#ImplicitTokenValidation)』に記載されているアクセストークンのハッシュ値計算方法に準じる。


# 3. CIBA 実装者の所感

ここでは、CIBA 実装者が気にするポイントを、CIBA 仕様策定過程の議論を交えながら、幾つか取り上げる。


## 3.1. リクエストオブジェクト

CIBA のドラフトを初めて読んだ時に最初に厄介だと思ったのは、バックチャネル認証リクエストのリクエストパラメーター群を、OIDC Core のリクエストオブジェクトと同じように（OIDC Core, [6. Passing Request Parameters as JWTs](https://openid.net/specs/openid-connect-core-1_0.html#JWTRequests)）、JWT にまとめて `request` リクエストパラメーターとして指定できるようにしてある点だった。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">CIBA はバックチャネル認証エンドポイントに認証リクエストを投げるところから始まるけど、このリクエストが JWT になる可能性があり、さらに FAPI RW API 用だとそれが必須になる予定。クライアント側が署名するので、リクエストオブジェクトと似た処理がサーバー実装側で必要となる。めんどい。</p>&mdash; Takahiko Kawasaki (@darutk) <a href="https://twitter.com/darutk/status/1052600169387061248?ref_src=twsrc%5Etfw">2018年10月17日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

リクエストオブジェクトが渡ってきた場合、サーバー側では、もし暗号化されていれば復号化し、それから署名の検証をおこなう。復号化処理および署名検証処理でそれぞれ鍵が必要であるが、それらを登録済みの JWK Set から検索する処理の実装が、まず面倒である。次に、アルゴリズムの違いで復号器や署名検証器の生成方法が異なるので、その点も面倒である。署名検証が終わったあとに、ペイロード部の `aud`、`iss`、`exp`、`iat`、`nbf`、`jti` を検証するのも面倒である。

ただ、ドラフト段階でリクエストオブジェクトの暗号化について言及がなかった（特に暗号関連のメタデータ定義が存在しなかった）ので、「暗号化をサポートする予定はあるのか？なければその旨仕様書に明記してほしい。あるならば暗号関連のメタデータを定義すべき」という要望を出したのが Issue 105 である。

* 【[Issue 105](https://bitbucket.org/openid/mobile/issues/105)】CIBA: encryption of backchannel authentication request

この結果、仕様書に次の一文が加わることとなった。

> _Note that encrypted JWT authentication requests are not supported._

おかげで **JWT の暗号化に対応しなくてもよくなった**ので、その分実装が楽になった。

次に気になったのは、`request` パラメーターが使われた場合、他のリクエストパラメーターの扱いをどうするか、という点だった。OIDC Core では、`request` に含めたリクエストパラメーターを再度 `request` 外に指定してもかまわない。どっちかにしか入れなくてもかまわない。FAPI だとまた要求仕様が異なる。

OIDC Core では、RFC 6749 との互換性のため、`request` パラメーターを使っている場合でも、`client_id` や `response_type` は指定しなければならない。例えそれらが `request` に指定された JWT の中に含まれていても、だ。逆に、JWT に含まれていた場合、JWT 外で指定された値と同一であることをチェックしなければならない。とにかくいろいろ面倒だ。

しかしながら、CIBA のバックチャネル認証エンドポイントは新規作成する仕様なわけだから、OIDC Core のような面倒を避けることが可能なはずだ。そこで、「`request` が使われていた場合、他のリクエストパラメーターの使用を禁止してはどうか？」という提案をおこなったのが、Issue 117 だ。

* 【[Issue 117](https://bitbucket.org/openid/mobile/issues/117)】CIBA: other request parameters when "request" is present

この結果、`request` パラメーターが使われた場合は他のリクエストパラメーターを禁止する方向でドラフトが変更されたが、依然として不明瞭な部分が残っていたので、ジョセフ・ヒーナンが Issue 128 を挙げた。

* 【[Issue 128](https://bitbucket.org/openid/mobile/issues/128)】ambiguities in 7.1.1 signed authentication request

最終的に、次のような仕様になった。

1. **`request` が使われた場合、他のリクエストパラメーター群を使用してはならない。**
2. **ただし、クライアント認証に関わるリクエストパラメーター群（`client_id`、`client_assertion` など）は、`request` 外に指定する。**
3. 逆に、クライアント認証に関わるリクエストパラメーター群を `request` 内に含めてはならない。

原文は次のとおり。

> *The signed authentication request JWT is passed as an `application/x-www-form-urlencoded` HTTP request parameter with the name `request`. Authentication request parameters MUST NOT be present outside of the JWT, in particular they MUST NOT appear as HTTP request parameters. Additional HTTP request parameters as required by the given client authentication method, however, MUST be included as `application/x-www-form-urlencoded` parameters (e.g. Mutual TLS client authentication uses `client_id` while JWT assertion based client authentication uses `client_assertion` and `client_assertion_type`).*

これにより、OIDC Core と比べて `request` の処理は大分楽になった。

あと、興味深いのは、**JWT の署名アルゴリズムが非対称鍵系に限定される**ことである。これにより、対称鍵系アルゴリズムである `HS256`、`HS384`、`HS512` は排除されることになる。OIDC の文脈においては、「クライアントシークレットも他の鍵と同様に JWK Set 内で管理する」という実装にでもなっていない限り、対称鍵系アルゴリズムの鍵の算出には別途対応が必要となるので（OIDC Core, [10.1. Signing](https://openid.net/specs/openid-connect-core-1_0.html#Signing)）、アルゴリズムを非対称鍵に限定するという仕様は実装の容易化に資する。実装者としてはありがたい。

なお、OIDC Core, [6.2.2.](https://openid.net/specs/openid-connect-core-1_0.html#UseRequestUri) で定義されている **`request_uri` パラメーターはバックチャネル認証リクエストには存在しない**ので安心してほしい。もしこれがあったら、実装はかなり大変なことになる。


## 3.2. クライアント認証

CIBA Core, 7.1. Authentication Request の第二段落には次のように書いてある。

> _**The Client MUST authenticate to the Backchannel Authentication Endpoint** using the authentication method registered for its client_id, such as the authentication methods from Section 9 of [OpenID.Core] or authentication methods defined by extension in other specifications._

つまり、**バックチャネル認証エンドポイントにおいてクライアント認証が必須**ということである。必然的に、**CIBA を利用できるのは Confidential クライアントだけ**であり、Public クライアントは利用できない、ということになる。

CIBA が言及している『OIDC Core, [9. Client Authentication](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication)』に列挙されているクライアント認証方式は次のとおり（`none` を除く）。

||クライアント認証方式|
|:-:|:--|
|1|`client_secret_basic`|
|2|`client_secret_post`|
|3|`client_secret_jwt`|
|4|`private_key_jwt`|

FAPI の文脈においては、『[OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound Access Tokens](https://datatracker.ietf.org/doc/draft-ietf-oauth-mtls/)』で定義されている下記のクライアント証明書系クライアント認証方式も考慮する必要がある。

||クライアント認証方式|
|:-:|:--|
|5|`tls_client_auth`|
|6|`self_signed_tls_client_auth`|

認可サーバーがサポートするクライアント認証方式については、通常、エンドポイント毎にメタデータが存在する。例えば、トークンエンドポイントでサポートするクライアント認証方式については `token_endpoint_auth_methods_supported`というメタデータが用意されている（[RFC 8414](https://tools.ietf.org/html/rfc8414)）。同様にして、イントロスペクションエンドポイント（[RFC 7662](https://tools.ietf.org/html/rfc7662)）については `introspection_endpoint_auth_methods_supported`、リボケーションエンドポイント（[RFC 7009](https://tools.ietf.org/html/rfc7009)）については `revocation_endpoint_auth_methods_supported`、というメタデータが用意されている。

そこで、バックチャネル認証エンドポイントでサポートするクライアント認証方式を列挙するためのメタデータとして `backchannel_authentication_endpoint_auth_methods_supported` を提案したのが、Issue 102 だ。

* 【[Issue 102](https://bitbucket.org/openid/mobile/issues/102)】CIBA: Metadata for client auth at backchannel endpoint

しかしながら、このメタデータが仕様に追加されることはなかった。なぜか？ これには、CIBA 仕様の編集者であるブライアン・キャンベルの考えがある。彼のコメントをそのまま転載しよう。

> *There's some historical weirdness to how client authentication has come to be represented (and named) in metadata. Client registration metadata has only a `token_endpoint_auth_method`, which despite the name has become the de facto place in the client data model to say how the client will authenticate to the AS when making any direct client -> AS call. The parameter name is a bit unfortunate but I believe it makes sense to have a single (backchannel) authentication method per client. RFC 8414 took a different direction and has `revocation_endpoint_auth_methods_supported` and `introspection_endpoint_auth_methods_supported` etc., which I believe was a mistake. I don't see a clear use case for needing or allowing a different set of auth methods for the different endpoints. And having a bunch of _supported parameters for different endpoints seems likely to clutter up the metadata document with a bunch of redundant info.*
>
>*I'd propose that some text be added into CIBA core that says that, for the backchannel authentication endpoint, the AS supports the same client authentication methods as indicated with `token_endpoint_auth_methods_supported`. And state that, for a client, `the token_endpoint_auth_method` is the authentication method registered for its `client_id` regardless of the endpoint being called.*

これは分かる。サポートするクライアント認証方式を表すメタデータをエンドポイント毎に用意するのは、実装者としては面倒だし、正直、エンドポイント毎にクライアント認証方式を変更可能にする必要があるユースケースを考え出すのも難しい。クライアント認証方式を表すメタデータは全エンドポイント共通で一つあればおそらく十分。ブライアンは*「RFC 8414 は `revocation_endpoint_auth_methods_supported` と `introspection_endpoint_auth_methods_supported` を定義し、異なる方向へ進んでしまったが、これは間違いだったと私は信じている」*とまで言っている。

この結果、**バックチャネル認証エンドポイントにおけるクライアント認証方式を表すための新たなメタデータが追加されることはなくなった**。かわりに、**サーバー側は `token_endpoint_auth_methods_supported`、クライアント側は `token_endpoint_auth_method` で代用する**こととなった。また、クライアント認証方式で JWT 系を使う際に、その JWT の署名アルゴリズムを表すメタデータである `token_endpoint_auth_signing_alg_values_supported`（サーバー側）と `token_endpoint_auth_signing_alg`（クライアント側）も同様の扱いとなる。

ところで、実は**バックチャネル認証リクエストでは `client_id` リクエストパラメーターは必須ではない**。この点は RFC 6749 の認可リクエストと異なる。では、バックチャネル認証エンドポイントの実装はどのようにしてクライアント識別子を特定するのか？

まず、クライアント認証方式が `client_secret_basic` と `client_secret_post` の場合、これは簡単である。なぜなら、クライアント ID の値が直接リクエストの中に含まれているからだ。

次に、クライアント認証方式が `client_secret_jwt` と `private_key_jwt` の場合だが、このときは `client_assertion` リクエストパラメーターの値として指定された JWT のペイロードに含まれる `iss` クレームの値をクライアント ID とみなして処理すればよい。

最後に、クライアント認証方式が `tls_client_auth` と `self_signed_tls_client_auth` の場合だが、実はこの認証方式にはクライアント ID を示す情報が含まれない。そのため、これらのクライアント認証方式を使う場合、`client_id` リクエストパラメーターを含めなければならない。

CIBA 仕様の『7.1. Authentication Request』の後半に追加された次の文は、上記のことを念頭に置いたものである。

> *When applicable, additional parameters required by the given client authentication method are also included (e.g. JWT assertion based client authentication uses `client_assertion` and `client_assertion_type` while Mutual TLS client authentication uses `client_id`).*


## 3.3. 認証コンテキストクラス

バックチャネル認証リクエストには `acr_values` というリクエストパラメーターがある。このリクエストパラメーターは OIDC Core にも存在し、いろいろ面倒なやつだ（参考→「[16. 認証コンテキストクラス](https://qiita.com/TakahikoKawasaki/items/185d34814eb9f7ac7ef3#16-%E8%AA%8D%E8%A8%BC%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%AF%E3%83%A9%E3%82%B9)」）。

`acr_values` には、ユーザー認証時に満たしてほしい認証コンテキストクラスを、望ましい順番に列挙する。OIDC Core では、認可サーバー側は、できるだけ指定された認証コンテキストクラスを満たそうと試みる。しかし、例えどれ一つ満たせなくても、実はエラーとはみなされない。なぜなら、`acr_values` を用いて認証コンテキストを指定した場合、任意という扱いになってしまうからだ。

これを必須としたい場合、つまり、どれかを満たせない限りエラーとみなしてほしい場合、`acr_values` 以外の方法で認証コンテキストを指定する必要がある。その方法は、大方の人が予想するよりもかなり面倒なもので、`claims` リクエストパラメーターを用いて指定する JSON 内の深い階層に `{"essential":true}` と書かなければならないのだ。下記は、`urn:mace:incommon:iap:silver` を必須とする例だ。

```json
{
  "id_token":
    {
      "acr":
        {
          "essential": true,
          "values": ["urn:mace:incommon:iap:silver"]
        }
    }
}
```

こんな面倒なことを誰がやるのかと思うかもしれないが、『[Financial-grade API - Part 2: Read and Write API Security Profile](https://openid.net/specs/openid-financial-api-part-2-ID2.html)』の『[5.2.3. Public Client](https://openid.net/specs/openid-financial-api-part-2-ID2.html#public-client)』に _“shall request user authentication at LoA 3 or greater by requesting **the `acr` claim as an essential claim** as defined in section 5.5.1.1 of [OIDC];”_ という要求事項があるため、FAPI Part 2 に対応するためには、この仕組みの実装が必要となる。

そういうわけで、バックチャネル認証リクエストにおいて ACR を必須とマークする手段を提供するつもりがあるかどうかを問い合わせたのが、Issue 103 だ。`claims` リクエストパラメーター（OIDC Core, [5.5. Requesting Claims using the "claims" Request Parameter](https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter)）の実装はかなり大変で、後から追加するのは厄介なので、最初に「`claims` をサポートするのかしないのか」をはっきりさせておきたかったのだ。

* 【[Issue 103](https://bitbucket.org/openid/mobile/issues/103)】CIBA: Means to require "acr" as "essential"

最終的に、**ACR を必須とマークする手段は提供しない**ことになった。ブライアンのコメント _“I think that something complicated like the "claims" request parameter from OIDC Core would be **overkill** in CIBA.”_ からもわかる通り、CIBA はできるだけ仕様を簡素化しようとしている。


## 3.4. クレーム指定

OIDC Core では、ID トークンに埋め込んで欲しいカスタムクレームをクライアント側から指定する手段が存在する。`claims` リクエストパラメーターがそれだ。（１）この手段をバックチャネル認証リクエストでも提供するのか、また、（２）`scope` リクエストパラメーターに `profile`、`email`、`address`、`phone` が含まれていた場合にそれらを『OIDC Core, [5.4. Requesting Claims using Scope Values](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims)』と同じ方法で解釈すべきか、という問い合わせをしたのが Issue 106 である。

* 【[Issue 106](https://bitbucket.org/openid/mobile/issues/106)】CIBA: Means to request claims to be embedded in the issued ID token

結果、**カスタムスコープを指定する方法は提供しない**、また、**`profile`、`email`、`address`、`phone` は OIDC Core と同様に解釈する**、ということが決まった。

`claims` リクエストパラメーターの値は、仕様書をよく読んで実装してみれば分かるが、JSON パーサーで特殊クラスのインスタンスに一気にマッピングできる類のものではなく、Map などの汎用クラスのインスタンスに変換したあとに手作業でパース処理をおこなう必要がある。つまり、面倒くさい。`claims` リクエストパラメーターをサポートしなくてよいのは、実装者としてはありがたい。


## 3.5. ヒント

バックチャネル認証リクエストには、対象となるユーザーを特定するためのヒントを含めなければならない。ヒントを指定するためのリクエストパラメーターとして、`login_hint_token`、`id_token_hint`、`login_hint` の三つがある。バックチャネル認証リクエストパラメーターは、これらのうち必ず一つを含まなければならず、また、同時に複数のヒントを含めてはならない。

`id_token_hint` と `login_hint` は OIDC Core に倣ったものだ。`login_hint_token` は CIBA で追加されたものであるが、そのフォーマットは何も規定されていない。

さて、リクエストのバリデーションの実装を開始すればすぐに気がつくが、

1. `login_hint_token` が含まれていたら `id_token_hint` と `login_hint` は含まれていてはならない。
2. `id_token_hint` が含まれていたら `login_hint_token` と `login_hint` は含まれていてはならない。
3. `login_hint` が含まれていたら `login_hint_token` と `id_token_hint` は含まれていてはならない。
4. `login_hint_token`、`id_token_hint`、`login_hint` のどれか一つが含まれていなければならない。

をチェックする処理は、もちろん実装可能であるが、美しいコードにならない。

「いずれか一つを指定」というメカニズムを実現するには、ヒント毎にリクエストパラメーターを用意するのではなく、次のようなリクエストパラメーター設計にしたほうがよい。

| リクエストパラメーター名 | 説明 |
|:--|:--|
| `hint_type` | ヒントの種類 |
| `hint` | ヒントの値 |

クライアントの実装、サーバーの実装、どちらにとってもこちらのほうが都合が良い。そこで提案したのが、Issue 123 だ。

* 【[Issue 123](https://bitbucket.org/openid/mobile/issues/123)】CIBA: hint and hint_type

しかしながら、この案は却下されることになる。ブライアンも、三つのパラメーターは awkward で、`hint`/`hint_type` のアプローチに利点があることは認めつつも、時期的なことを考えると、変更を加えるほどではないという意見だった。

私の案のほうが良いと今でも思っているが、MODRNA ワーキンググループが CIBA Core の Public Review 入りを急いでいたことは分かっていたので、食い下がることはしなかった。

ただし、Authlete の実装には私の考え方が反映される。というか、CIBA 対応認可サーバーのサンプル実装をしている池田君も、Consumption Device / Authentication Device のシミュレーターを実装しているヘンリックさんも同意見であり、現実世界で実際に CIBA 実装に携わっているデベロッパーの多数意見（サンプル数＝３）が `hint`/`hint_type` アプローチを推しているわけなので、デベロッパー向けの製品である Authlete はそれに従う。

具体的には、バックチャネル認証リクエストを解析する Authlete の `/api/backchannel/authentication` API が返すレスポンス（<code>[BackchannelAuthenticationResponse](https://authlete.github.io/authlete-java-common/com/authlete/common/dto/BackchannelAuthenticationResponse.html)</code>）には、`loginHintToken`、`idTokenHint`、`loginHint` というレスポンスパラメーターはなく、代わりに `hintType`（<code>[UserIdentificationHintType](https://authlete.github.io/authlete-java-common/com/authlete/common/types/UserIdentificationHintType.html)</code>）と `hint`（`String`）というレスポンスパラメーターが含まれる。

なお、Authlete 共用サーバーのバージョンは 1.1 と古く、CIBA には対応していない（FAPI にも対応していない）。CIBA 対応済みの Authlete を試用したい場合は sales@authlete.com まで問い合わせてほしい。


## 3.6. その他

他にも、`client_notification_token` の値に使える文字種とその長さ（[Issue 104](https://bitbucket.org/openid/mobile/issues/104)）、PUSH モードで発行される ID トークンには `urn:openid:params:jwt:claim:rt_hash` が含まれるが他のモードでは含まれないのは意図的なのか（[Issue 127](https://bitbucket.org/openid/mobile/issues/127)）、PUSH モード時のエラーコードとして `expired_token` って使う？（[Issue 143](https://bitbucket.org/openid/mobile/issues/143)）、など、実装者の立場から明確化してほしい点を中心にいろいろ Issue を挙げた。ここに挙げていない新しい発見もあると思うので、興味があれば MODRNA ワーキンググループの [Issue リスト](https://bitbucket.org/openid/mobile/issues)に目を通すとよいだろう。

余談であるが、仕様書内の大量の typo や文法ミスを指摘しまくった（[Issue 99](https://bitbucket.org/openid/mobile/issues/99)、[Issue 110](https://bitbucket.org/openid/mobile/issues/110)、[Issue 129](https://bitbucket.org/openid/mobile/issues/129)）のは日本人の私である。これは、typo や文法間違いは、英文を一語一語丁寧に読む（読まざるを得ない）非ネイティブスピーカーのほうが気がつきやすいことを示している。こういう貢献の仕方もあるので、日本人の皆さんも気後れすることなく OpenID Foundation の[ワーキンググループ](https://openid.net/wg/)の活動に参加してほしい。


## 3.7. 今後

2018 年末に CIBA Core が Public Review に附されたのは、議論が出尽くして枯れた仕様となったから、ではなく、欧州の金融政策の日程に間に合わせるため、という理由が大きい。とはいえ、元となった仕様とかなり差分があるので、ここで一度 CIBA Core の Implementer's Draft 第一版を世に出すことの意義は大きい。

FAPI は、Implementer's Draft 第一版リリース後も議論が続き、2018 年 10 月末に第二版がリリースされた。これと同じように、CIBA Core についても、第一版リリース後も議論が続き、やがては第二版が出るだろう。現に Public Review 期間中にも仕様に影響しそうな Issue が幾つか挙がっている。この分野に携わる者は [MODRNA ワーキンググループ](https://openid.net/wg/mobile/)の議論を継続的に追っていく必要がある。

なお、CIBA Core の Public Review 入りを受け、[FAPI ワーキンググループ](https://openid.net/wg/fapi/)では FAPI-CIBA プロファイルの議論が再開された。「FAPI では PUSH モード禁止」などの制約事項や追加要求事項がまとめられることになる。こちらもあまり時間をおかずに Public Review 入りすることになるだろう。


# 4. Authlete の CIBA 実装

Authlete は、CIBA をサポートするため、新たに次の 4 つの API を追加した。

|1|`/api/backchannel/authentication`|
|:--|:--|
|用途|バックチャネル認証リクエストを解析する|
|リクエスト仕様|<code>[BackchannelAuthenticationRequest](https://authlete.github.io/authlete-java-common/com/authlete/common/dto/BackchannelAuthenticationRequest.html)|
|レスポンス仕様|<code>[BackchannelAuthenticationResponse](https://authlete.github.io/authlete-java-common/com/authlete/common/dto/BackchannelAuthenticationResponse.html)|

|2|`/api/backchannel/authentication/issue`|
|:--|:--|
|用途|`auth_req_id` を発行する|
|リクエスト仕様|<code>[BackchannelAuthenticationIssueRequest](https://authlete.github.io/authlete-java-common/com/authlete/common/dto/BackchannelAuthenticationIssueRequest.html)|
|レスポンス仕様|<code>[BackchannelAuthenticationIssueResponse](https://authlete.github.io/authlete-java-common/com/authlete/common/dto/BackchannelAuthenticationIssueResponse.html)|

|3|`/api/backchannel/authentication/fail`|
|:--|:--|
|用途|バックチャネル認証エンドポイントからのエラー応答を生成する|
|リクエスト仕様|<code>[BackchannelAuthenticationFailRequest](https://authlete.github.io/authlete-java-common/com/authlete/common/dto/BackchannelAuthenticationFailRequest.html)|
|レスポンス仕様|<code>[BackchannelAuthenticationFailResponse](https://authlete.github.io/authlete-java-common/com/authlete/common/dto/BackchannelAuthenticationFailResponse.html)|

|4|`/api/backchannel/authentication/complete`|
|:--|:--|
|用途|ユーザー認証・認可後の完了処理をおこなう|
|リクエスト仕様|<code>[BackchannelAuthenticationCompleteRequest](https://authlete.github.io/authlete-java-common/com/authlete/common/dto/BackchannelAuthenticationCompleteRequest.html)|
|レスポンス仕様|<code>[BackchannelAuthenticationCompleteResponse](https://authlete.github.io/authlete-java-common/com/authlete/common/dto/BackchannelAuthenticationCompleteResponse.html)|

また、`/api/auth/token` API の実装に CIBA サポートを追加した。

これらの API を下図のように組み合わせて利用することで、CIBA の各フローを実装することができる。

<img width="767" alt="ciba-poll-mode-authlete.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/1249ead9-3e12-7fb0-4440-4817b29a9495.png">

<img width="766" alt="ciba-ping-mode-authlete.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/71decc74-78fb-012f-d003-15393ebca860.png">

<img width="767" alt="ciba-push-mode-authlete.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/410197e6-13f8-ee5d-cd23-90bd5e057c07.png">

CIBA 対応認可サーバーの実装方法ついては次の記事が詳しい。

* **[Authlete を使って CIBA 対応の認可サーバーを作る](https://qiita.com/hidebike712/items/8fc2938055d0b49cfc0a)**

なお、Authlete は意図的にユーザーデータ管理・ユーザー認証をおこなわない設計になっているので（参照→『[OAuth 2.0 / OIDC 実装の新アーキテクチャー](https://qiita.com/TakahikoKawasaki/items/b2a4fc39e0c1a1949aab)』）、認可サーバーの実装と、その実装と認証デバイスとのやりとりは、Authlete 利用者に自ら実装してもらうことになる。

オープンソースとして公開している認可サーバーのサンプル実装である [java-oauth-server](https://github.com/authlete/java-oauth-server) の最新版は CIBA 対応済みである。ただし、CIBA を試すには、バックエンドに CIBA 対応済みの Authlete（バージョン 2.1 以降）が必要である。試用したい場合は sales@authlete.com まで問い合わせてほしい。

また、今回新規開発した認証デバイスとクライアントのシミュレーターについては、それらをホスティングするウェブサイトを公開する予定である。


# おわりに

FAPI の Implementer's Draft 第二版には、CIBA は FAPI の構成要素の一つであると書かれている。イギリスのオープンバンキングも CIBA の採用を決定した。オーストラリアの金融機関も CIBA に大きな関心を示している。日本ではまだ馴染みが薄いかもしれないが、海外、特に欧州では「CIBA を使うのが当たり前」という雰囲気さえできつつある。

CIBA が可能にする新しいユースケースは、新しいビジネスを生み出す。このビジネスチャンスを皆さんもとらえてほしい。Authlete 社も、（おそらく）世界で最初の商用 CIBA 実装を武器に、新しいマーケットに進出する！（新年の抱負）

今年もよろしくお願いします。