図解デバイスフロー（RFC 8628）

# はじめに

2019 年 8 月に公開された [RFC 8628](https://tools.ietf.org/html/rfc8628)（OAuth 2.0 Device Authorization Grant）、いわゆる『**デバイスフロー**』（Device Flow）について説明します。

デバイスフローは、[RFC 6749](https://tools.ietf.org/html/rfc6749)（The OAuth 2.0 Authorization Framework）で定義されているフロー群と同様に、アクセストークンを発行するためのフローです。

そもそも別途新たにフローを作成した理由ですが、それについては仕様書の冒頭に次のように書かれています。

> The OAuth 2.0 device authorization grant is designed for Internet-connected devices that either lack a browser to perform a user-agent-based authorization or are input constrained to the extent that requiring the user to input text in order to authenticate during the authorization flow is impractical.

すなわち、Web ブラウザを搭載していないデバイスや文字入力方法などに著しい制限のあるデバイス上で動くクライアントアプリケーションのために、従来のものとは異なるフローが必要となったからです。

それではデバイスフローの解説を始めます。


# 1. デバイスフロー解説

**（１）** 何らかのデバイスがあります。よくある例はスマートテレビです。このデバイスの上でクライアントアプリケーションが動きます。
![device_flow_ja-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/028ee103-13b2-4134-d899-05e41d7549f0.png)

<br/>

**（２）** 認可サーバーを用意します。

![device_flow_ja-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/31ca823f-00ac-b702-9013-eb4f8831b9bc.png)

<br/>

**（３）** デバイスフローをサポートする認可サーバーは、**デバイス認可エンドポイント**（Device Authorization Endpoint）を用意します。

![device_flow_ja-03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/7f950cec-6d0f-7323-21a5-34bec7166771.png)

<br/>

**（４）** クライアントアプリケーションは、デバイス認可エンドポイントにリクエストを投げます。

![device_flow_ja-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/14048978-0367-e003-d295-d806e7fc05b6.png)

リクエストの仕様は [RFC 8628](https://tools.ietf.org/html/rfc8628) の [3.1. Device Authorization Request](https://tools.ietf.org/html/rfc8628#section-3.1) に定義されています。

|パラメーター|要否|説明|
|:--|:-:|:--|
|client_id|必須|クライアント ID|
|scope|任意|スコープ|

下記は仕様書に挙げられているリクエストの例です。

```http
POST /device_authorization HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

client_id=1406020730&scope=example_scope
```

<br/>

**（５）** デバイス認可エンドポイントは、レスポンスを返します。

![device_flow_ja-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/a242cdf2-db63-6727-db76-d65a96e94fe5.png)

レスポンスの仕様は [RFC 8628](https://tools.ietf.org/html/rfc8628) の [3.2. Device Authorization Response](https://tools.ietf.org/html/rfc8628#section-3.2) に定義されています。

|パラメーター|要否|説明|
|:--|:-:|:--|
|device_code|必須|Device Code|
|user_code|必須|User Code|
|verification_uri|必須|エンドユーザー検証 URI|
|verification_uri_complete|任意|User Code を含むエンドユーザー検証 URI|
|expires_in|必須|Device Code と User Code の有効秒数|
|interval|任意|トークンリクエストの最小ポーリング間隔秒数|

下記は仕様書に挙げられているレスポンスの例です。

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

{
  "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
  "user_code": "WDJB-MJHT",
  "verification_uri": "https://example.com/device",
  "verification_uri_complete":
    "https://example.com/device?user_code=WDJB-MJHT",
  "expires_in": 1800,
  "interval": 5
}
```

**User Code**（正式には End-User Verification Code）と **Device Code**（正式には Device Verification Code）が新規発行される点に注目です。これらは、後述するエンドユーザー検証エンドポイントへのリクエストやトークンリクエストの際に利用します。

<br/>

**（６）** クライアントは、デバイス認可レスポンスに含まれている `verification_uri` の値と `user_code` の値を表示します。

![device_flow_ja-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/ea5c78b8-5c4a-d7c6-cec8-50f9528e3e83.png)

<br/>

**（７）** よくある実装では、ユーザーの利便性のため、`verification_uri` または（もしデバイス認可レスポンスに含まれていたのならば）`verification_uri_complete` の値を表す QR コードも表示します。

![device_flow_ja-07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/56f4a14d-e6f1-21e1-c7bf-6a72bcf00f07.png)

<br/>

**（８）** `verification_uri` は、認可サーバーが用意している**エンドユーザー検証エンドポイント**（End-User Verification Endpoint）の URI を表しています。

![device_flow_ja-08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/4c4bcadb-9c91-01f7-37a4-348e1adbc97f.png)

`verification_uri_complete` もエンドユーザー検証エンドポイントの URI を表していますが、こちらにはパスやクエリーパラメーターなどに User Code が含まれています。（５）に掲載したデバイス認可レスポンスの例を参照してください。

<br/>

**（９）** ユーザーは Web ブラウザを用意します。典型的には、パソコンやスマートフォンに搭載されている Web ブラウザです。

![device_flow_ja-09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/db45124e-37ed-47e8-dcd3-1b4a134854dc.png)

<br/>

**（１０）** Web ブラウザを用いてエンドユーザー検証エンドポイントにアクセスします。

![device_flow_ja-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/d49dd6ca-575c-1f8e-9192-0fc551e58bc5.png)

<br/>

**（１１）** エンドユーザー検証エンドポイントは、ユーザー認証や User Code 入力のための HTML ページを返却します。

![device_flow_ja-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/307adcad-5a16-6570-141f-8d0a9a0d07ea.png)
<sup>注：`verification_uri_complete` が用いられた場合、User Code 入力欄には事前に値がセットされているでしょう。</sup>

<br/>

**（１２）** ユーザーは求められている値を入力し、サーバーに送信します。

![device_flow_ja-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/735df146-0850-f73f-0b90-2cf8f4efdde3.png)

<br/>

**（１３）** 認可サーバーはユーザー認証と User Code 検証をおこないます。

![device_flow_ja-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/b419e377-58e8-f880-30d6-b3e73cd95d45.png)

<br/>

**（１４）** 認可リクエストを承認するかどうかユーザーに確認をとるための画面を返却します。

![device_flow_ja-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/11a91ac1-218c-ffdc-bae1-65bd326a0a39.png)
<sup>注：確認画面を挟まずにすぐに処理結果画面を表示する実装もありえます。</sup>

<br/>

**（１５）** ユーザーによる認否の応答をサーバーに渡します。

![device_flow_ja-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/57ac40f4-01ef-45c2-d36d-1c285e130f65.png)

<br/>

**（１６）** 承認/否認処理を完了させ、処理結果を返します。

![device_flow_ja-16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/df2e3065-a272-0b17-30bd-be67bdd83856.png)

<br/>

**（１７）** 一方のクライアントアプリケーションは、デバイス認可エンドポイントからレスポンスを受け取ったあと、最終結果が得られるまでトークンリクエストを繰り返します。

![device_flow_ja-17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/0a8dcb27-7088-aadb-2fb4-7a78e677fbf2.png)

トークンリクエストの仕様は [RFC 8628](https://tools.ietf.org/html/rfc8628) の [3.4. Device Access Token Request](https://tools.ietf.org/html/rfc8628#section-3.4) に定義されています。

|パラメーター|要否|説明|
|:--|:-:|:--|
|grant_type|必須|固定値 `urn:ietf:params:oauth:grant-type:device_code`|
|device_code|必須|Device Code|
|client_id|必須|クライアント ID|

下記は仕様書に挙げられているトークンリクエストの例です。

```http
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code
&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
&client_id=1406020730
```

`grant_type` が `urn:ietf:params:oauth:grant-type:device_code` となっており、このトークンリクエストがデバイスフローのものであることを示しています。デバイスフローのトークンリクエストでは `device_code` リクエストパラメーターが必須です。デバイス認可レスポンスに含まれる `device_code` の値を、トークンリクエストの `device_code` の値として用います。

<br/>

**（１８）** トークンレスポンスが返されます。

![device_flow_ja-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/d49b7e27-6b95-2502-f215-23b2e5a4d8f0.png)

トークンレスポンスがエラーを示していても、その**エラーコードが `authorization_pending` もしくは `slow_down` の間は、クライアントアプリケーションはトークンリクエストを繰り返します**。具体的には、前述の（１６）の承認/否認処理が完了するまでは、もしくは、Device Code の有効期限が切れるまでは、トークンエンドポイントは `authorization_pending` エラーもしくは `slow_down` エラーを返し続けます。

成功応答を受け取るか、もしくは `authorization_pending` と `slow_down` 以外のエラーコードを受け取った場合、クライアントアプリケーションはそれ以上のトークンリクエストをおこなわず、デバイスフローの処理を終えます。

<br/>

**（まとめ）**

![device_flow_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/bfd0e3b3-fa53-46fe-4d4c-3bc4bdb11d9b.png)


# 2. User Code

QR コード等の入力簡略化手段が提供されない限り、ユーザーは User Code を目視で手入力しなければなりません。そのため、User Code の値は、ある程度のエントロピーを確保しつつもあまり長過ぎず、また、誤認しやすい文字を含まないものにすることが望ましいと言えます。[RFC 8628](https://tools.ietf.org/html/rfc8628) の [6.1. User Code Recommendations](https://tools.ietf.org/html/rfc8628#section-6.1) には、このような User Code の値に関する推奨事項が記述されています。

同セクションに挙げられている、User Code を構成する文字セットの例の一つ目は、`BCDFGHJKLMNPQRSTVWXZ` の 20 文字で構成されます。アルファベット 26 文字から母音（`AEIOUY`）を除いたものです。大文字小文字の違いは無視し、可読性のためにダッシュ等の区切り文字を含めてもよいとしています。ただし、区切り文字の有無は、User Code 比較処理の際は無視すべきとされています。仕様書では、この文字セットを用いた User Code の値の例として `WDJB-MJHT` を挙げています。

文字セットの例の二つ目は、`0123456789` の 10 文字で構成されます。単純に数字のみから構成される文字セットです。仕様書では `019-450-730` が例として挙げられています。


# 3. デバイスフローの実装

ここでは、**[Authlete](https://www.authlete.com/ja/)**（オースリート）のデバイスフローの実装について紹介します。

Authlete はバージョン 2.1 以降でデバイスフローをサポートしています。2020 年 2 月に Authlete 共用サーバー（api.authlete.com）のバージョンが 2.1 に更新されたので、専用サーバーを契約せずとも共用サーバーでデバイスフローを試すことができます。

## 3.1 サービスの設定

Authlete 2.1 で、サービス管理画面に『デバイスフロー』というタブが新設されました。デバイスフローを利用するためには、このタブ内の項目を設定する必要があります。

![device_flow_tab.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/b441bb58-db79-41ee-4602-0a26ea094aec.png)

|設定項目|説明|
|:--|:--|
|デバイス認可エンドポイント|デバイス認可エンドポイントの URL。この項目は OAuth 2.0 Device Authorization Grant (デバイスフロー) で定義されている `device_authorization_endpoint` メタデータに対応します。ASCII 文字のみで構成し、200 文字を超えてはなりません。スキーム部は `https` でなければなりません。<br/><br/>この項目はディスカバリーエンドポイントの応答に含まれる `device_authorization_endpoint` の値として使用されます。また、JWT ベースのクライアント認証 (`client_secret_jwt` または `private_key_jwt`) が使われる場合、クライアントアサーション (`client_assertion`) 内の `aud` クレームの値とこの項目の値が比較されます。|
|検証 URI|検証 URI。デバイス認可エンドポイントの応答に含まれる `verification_uri` の値として使われます。ASCII 文字のみで構成し、200 文字を超えてはなりません。スキーム部は `https` でなければなりません。<br/><br/>デバイスフローを使うためには、この項目をあらかじめ設定しておかなければなりません。そうしておかないと、`/api/device/authorization` API をコールした時にエラーが返されます。|
|プレースホルダー付き検証 URI|ユーザーコード用のプレースホルダーを含む検証 URI。ASCII 文字のみで構成し、200 文字を超えてはなりません。スキーム部は `https` でなければなりません。<br/><br/>この項目が設定されており、その値に固定文字列 `USER_CODE` が含まれていれば (例: `https://example.com/verification?user_code=USER_CODE`)、デバイス認可エンドポイントの応答に含める `verification_uri_complete` を生成する際に利用されます。Authlete は固定文字列 `USER_CODE` を実際のユーザーコードで置き換えます。 この項目が設定されていない、もしくは、固定文字列 `USER_CODE` を含んでいない場合、`verification_uri_complete` はデバイス認可エンドポイントの応答に埋め込まれません。|
|検証コード有効期間|デバイス検証コード (`device_code`) とユーザー検証コード (`user_code`) の有効期間秒数。この項目はデバイス認可エンドポイントの応答に含まれる `expires_in` の値として使用されます。<br/><br/>デバイスフローを使うためには、正の値をあらかじめ設定しておかなければなりません。そうしておかないと、`/api/device/authorization` API をコールした時にエラーが返されます。|
|ポーリング間隔|トークンエンドポイントへのポーリングリクエスト間の最小秒数。値は 0 から 65,535 の間でなければなりません。値がゼロでない限り、この項目はデバイス認可エンドポイントの応答に含まれる `interval` の値として使用されます。|
|ユーザーコード文字セット|ユーザーコード (`user_code`) の文字セット。<br/><br/>`BASE20` を選択すると、ユーザーコードは BCDFGHJKLMNPQRSTVWXZ (大文字・非母音の 20 文字) で構成されます (例: `WDJBMJHT`)。`NUMERIC` を選択すると、ユーザーコードは 0123456789 で構成されます (例: `019450730`)。この項目が設定されていない場合、デフォルト値として `BASE20` が使用されます。<br/><br/>Authlete が生成するユーザーコードにはダッシュや他の区切り文字は含まれないので注意してください。|
|ユーザーコード長|ユーザーコード (`user_code`) の長さ。 値は 0 から 255 の間でなければなりません。<br/><br/>値がゼロの場合、ユーザーコードの長さは、文字セット `BASE20` 用には 8 (エントロピー = 20^8)、文字セット `NUMERIC` 用には 9 (エントロピー = 10^9) になります。 この項目の値はユーザーコードのエントロピーに直接影響します。小さ過ぎる値は、`UNIQUE` 制約のため、データベースエラーを頻発させるでしょう。大き過ぎる値は、ユーザーコードを入力する気をユーザーから奪うでしょう。ですので、極端な値は設定しないでください。|

加えて、『認可』タブ内の「サポートする認可種別」で `DEVICE_CODE` をチェックします。`DEVICE_CODE` は `urn:ietf:params:oauth:grant-type:device_code` を表しています。
![grant_types_supported.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/8103d4d1-9d79-55dd-b07f-d65ad9b3a50f.png)


# 3.2. クライアントの設定

クライアント側でも、『認可』タブ内の「認可種別」で `DEVICE_CODE` をチェックします。
![grant_types.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/8026e4f4-3def-161e-6507-3ef1c7c18680.png)


# 3.3. 認可サーバー

オープンソースの認可サーバー実装である [java-oauth-server](https://github.com/authlete/java-oauth-server) には、デバイス認可エンドポイントとエンドユーザー検証エンドポイントが実装されています。それぞれのパスは `/api/device/authorization` と `/api/device/verification` です。ダウンロードして、いろいろ試してみてください。


# 3.4. Authlete API

デバイスフローをサポートするため、Authlete に次の API 群が追加されました。

- `/api/device/authorization` （デバイス認可リクエストの処理）
- `/api/device/complete` （承認/否認処理）
- `/api/device/verification` （User Code の情報取得と検証）

認可サーバーと Authlete API 群の関係は次の図のとおりです。

![device_flow+Authlete_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/478c6a2b-626f-7383-608a-0bf77e326a6b.png)

API の詳細仕様については、[authlete-java-common](https://github.com/authlete/authlete-java-common) ライブラリの [JavaDoc](http://authlete.github.io/authlete-java-common/) で、`Device` で始まるクラス群の説明を参照してください。


# 3.5. Authlete のデバイスフロー実装の良いところ

普通の実装に比べて Authlete の実装のほうが良いと思われる点は次のとおりです。

 デバイス認可エンドポイントが [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html)（Resource Indicators for OAuth 2.0）の `resource` パラメーターに対応している。
- デバイス認可エンドポイントが [RAR](https://datatracker.ietf.org/doc/draft-ietf-oauth-rar/?include_text=1)（Rich Authorization Requests for OAuth 2.0）の `authorization_details` パラメーターに対応している。
- デバイス認可エンドポイントが [RFC 7523](https://tools.ietf.org/html/rfc7523)（JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants）や [RFC 8705](https://www.rfc-editor.org/rfc/rfc8705.html)（OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens）で定義されている[クライアント認証](https://qiita.com/TakahikoKawasaki/items/63ed4a9d8d6e5109e401 "OAuth 2.0 クライアント認証")方式に対応している。
- エンドユーザー検証エンドポイントの実装の自由度が高い。特に、ユーザー認証方法や生成する HTML の構成について、何も制限がかからない。
- デバイス認可リクエストの `scope` に `openid` が含まれている場合、アクセストークンに加えて ID トークンも発行される。この動作は [RFC 8628](https://tools.ietf.org/html/rfc8628) に対する大きな拡張だが、既に他の実装が存在しており（例：マイクロソフト社）、また、顧客からの要望も強い。
- ID トークンのサポートに伴い、デバイス認可エンドポイントが `acr_values` パラメーターに対応している。これは、[CIBA](https://qiita.com/TakahikoKawasaki/items/9b9616b999d4ce959ba3 "世界最先端の認証認可技術、実装者による『CIBA』解説")（Client Initiated Backchannel Authentication）の[バックチャネル認証リクエスト](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0-ID2.html#rfc.section.7.1)の `acr_values` パラメーターに倣ったものである。


# おわりに

FAPI（Financial-grade API）や CIBA（Client Initiated Backchannel Authentication）だけではなく、デバイスフローでも Authlete を御活用ください！ お問い合わせは[コンタクトフォーム](https://www.authlete.com/ja/contact/)またはメール（sales@authlete.com）でよろしくお願いします！