Table of Contents
いわゆる「デバイスフロー」は、Web ブラウザ非搭載のデバイスや、文字入力が困難なデバイスが API クライアントとなる場合に、ユーザーの承認に基づいてアクセストークンを発行する認可フローです。RFC 8628 (OAuth 2.0 Device Authorization Grant) によって定義されています。
本記事では、Authlete を用いたデバイスフロー対応認可サーバーの構成と、Authlete の設定手順について説明します。
本機能は Authlete バージョン 2.1 以降にて利用可能です。
認可サーバーがデバイスフローに対応するためには、以下のエンドポイントおよび「検証 URI」の、新設・改修が必要です。これらを実装するための機能を、Authlete は API として提供しています。
デバイス認可エンドポイント
トークンエンドポイント
「検証 URI」
本セクションではデバイスフローに対応するための設定を説明します。Authlete サービスと、同フローを用いるクライアントの、両方の設定が必要です。
管理者コンソールから以下の項目を設定します。
タブ | 項目 | 設定内容 |
---|---|---|
認可 | サポートする認可種別 | DEVICE_CODE を有効化 |
デバイスフロー | デバイス認可エンドポイント | デバイス認可エンドポイントの URL 例: https://as.example.com/device_authorization |
デバイスフロー | 検証 URI | エンドユーザーに提示する verification_uri の値 例: https://as.example.com/device |
デバイスフロー | プレースホルダー付き検証 URI | エンドユーザーに(一般的には QR コード等を用いて)提示する verification_uri_complete の値 例: https://as.example.com/device?user_code=USER_CODE |
デバイスフロー | 検証コード有効期間 | デバイス検証コード (device_code) とユーザー検証コード (user_code) の有効期間秒数 例: 600 |
デバイスフロー | ポーリング間隔 | トークンエンドポイントへのポーリングリクエスト間の最小秒数 例: 5 |
デバイスフロー | ユーザーコード文字セット | 生成する user_code の文字セット 例: BASE20 |
デバイスフロー | ユーザーコード長 | 生成する user_code の文字数 例: 8 |
クライアントアプリ開発者コンソールにアクセスし、以下の項目を設定します。
タブ | 項目 | 設定内容 |
---|---|---|
基本情報 | クライアントタイプ | PUBLIC を選択 |
認可 | 認可種別 | DEVICE_CODE を有効化 |
認可 | [トークンエンドポイント]クライアント認証方式 | NONE を選択 |
本記事では、デバイスは「コンフィデンシャルではないクライアント 」を想定しています。もしデバイスがコンフィデンシャルクライアントである場合には、「クライアントタイプ」および「クライアント認証方式」についてそれぞれ適切な値を選択してください。
以下は、クライアントからのデバイス認可リクエストを起点に、エンドユーザーから提示された user_code の検証を行い、クライアントからのトークンリクエストに対して応答する例です。
ここでは、クライアントが認可サーバーに対して以下の「デバイス認可リクエスト」を送信したとします(手順 #2)。(以下の例はいずれも、読みやすさのために折り返しています)
POST /device_authorization HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
...
client_id=...&scope=openid+profile+read
認可サーバーは Authlete の /device/authorization にこのリクエストの内容を転送し、処理を依頼します(手順 #3, 4)。
curl -s -X POST $apiUrl/device/authorization \
-u $apiKey:$apiSecret \
-H 'Content-type: application/json' \
-d '{"parameters": "client\_id=...&scope=openid+profile+read
"}'
{
"type": "deviceAuthorizationResponse",
"resultCode": "A220001",
"resultMessage":
"[A220001] The device authorization request was
processed successfully.",
"action": "OK",
"deviceCode":
"-jxwQ_7MEdR3SqS86bEg1ONUYdwGmSYjqH8eIBZ1c3U",
"responseContent":
"{\"user_code\":\"TXBBPHDZ\",
\"device_code\":
\"-jxwQ_7MEdR3SqS86bEg1ONUYdwGmSYjqH8eIBZ1c3U\",
\"interval\":5,
\"verification_uri_complete\":
\"https://as.example.com/device?user_code=TXBBPHDZ\",
\"verification_uri\":
\"https://as.example.com/device\",
\"expires_in\":600}",
"userCode": "TXBBPHDZ",
"verificationUri":
"https://as.example.com/device",
"verificationUriComplete":
"https://as.example.com/device?user_code=TXBBPHDZ",
...
}
認可サーバーは Authlete から返却された “responseContent” を、デバイス認可レスポンスの内容として、クライアントに返却します(手順 #5。詳細は省略)。
クライアントは、認可サーバーから返却されたレスポンスに含まれる “device_code” の値を用いて、認可サーバーに対し、「デバイスアクセストークンリクエスト」を送信します(後述)。
その一方、同じくレスポンスから取得した “user_code” の値を、認可サーバーの「検証 URI」に提示するよう、エンドユーザーに促します(手順 #6)。
どのように提示するかはクライアントに任されています。以下は、先のレスポンスに含まれる “verification_uri” の値と共に、エンドユーザーに示す例 (RFC 8628 の例 に加筆) です。
+-----------------------------------------------+ | | | Using a browser on another device, visit: | | https://as.example.com/device | | | | And enter the code: | | TXBBPHDZ | | | +-----------------------------------------------+
また以下は、同じくレスポンスに含まれる “verification_uri_complete” の値を QR コードにエンコードし、エンドユーザーにスキャンしてもらう例 (RFC 8628 の例 に加筆) です。
+-------------------------------------------------+ | | | Scan the QR code or, using +------------+ | | a browser on another device, |[_].. . [_]| | | visit: | . .. . .| | | https://as.example.com/device | . . . ....| | | |. . . . | | | And enter the code: |[_]. ... . | | | TXBBPHDZ +------------+ | | | +-------------------------------------------------+
なんらかの方法でエンドユーザーから user_code を受け取った認可サーバーの「検証 URI」では(手順 #7)、Authlete の /device/verification API に user_code を転送し、処理を依頼します(手順 #8, 9)。
curl -s -X POST $apiUrl/device/verification \ -u $apiKey:$apiSecret \ -H 'Content-type: application/json' \ -d '{"userCode":"TXBBPHDZ"}'
{
"type": "deviceVerificationResponse",
"resultCode": "A224001",
"resultMessage": "[A224001] The user code is valid.",
"action": "VALID",
"claimNames": [
...
],
"clientId": ...,
"clientName": "Demo Client",
"scopes": [
{
"defaultEntry": false,
"name": "openid"
},
{
"defaultEntry": false,
"name": "profile"
},
{
"defaultEntry": false,
"name": "read"
}
],
...
}
このレスポンスには、user_code の値の検証に成功したことの他に、アクセストークンを要求しているクライアントの情報や、どのようなスコープやクレームを求めているかといった情報が含まれています。
認可サーバーはこれらをもとに、エンドユーザーに同意確認を行うことになります。
必要に応じて認可サーバーは、エンドユーザーを認証し、上記の情報から「どのクライアントがどのようなアクセス権を要求しているか」をエンドユーザーに提示します(手順 #10, 11)。
そしてエンドユーザーのユーザー識別子と、発行するトークンのプロパティ(スコープやクレームなど)が確定した段階で、Authlete の /device/complete API を呼び出し、処理を依頼します(手順 #12, 13)。
curl -s -X POST $apiUrl/device/complete -u $apiKey:$apiSecret -H 'Content-type: application/json' -d '{"userCode":"TXBBPHDZ", "result":"AUTHORIZED", "subject":"testuser01"}'
{
"type": "deviceCompleteResponse",
"resultCode": "A241001",
"resultMessage": "[A241001] The API call was processed successfully.",
"action": "SUCCESS"
}
この後認可サーバーは処理が完了した旨をエンドユーザーに示し、検証処理を終了します(手順 #14)。
前述の通りクライアントは、 “device_code” の値を用いて、認可サーバーに対し「デバイスアクセストークンリクエスト」を送信します(手順 #a)。基本的には、user_code の検証が完了してアクセストークンが取得できるまで、繰り返しリクエストを行います(ポーリングします)。
curl -s -X POST $apiUrl/auth/token -u $apiKey:$apiSecret -H 'Content-type: application/json' -d '{"parameters": "client_id=... &grant_type=urn:ietf:params:oauth:grant-type:device_code &device_code=-jxwQ_7MEdR3SqS86bEg1ONUYdwGmSYjqH8eIBZ1c3U"}'
{
"type": "tokenResponse",
"resultCode": "A242307",
"resultMessage":
"[A242307] The device authorization request has not been authorized yet.",
"action": "BAD_REQUEST",
"grantType": "DEVICE_CODE",
"responseContent":
"{\"error_description\":
\"[A242307] The device authorization request has not been authorized yet.\",
\"error\":\"authorization_pending\",
\"error_uri\":\"https://docs.authlete.com/#A242307\"}",
...
}
{ "type": "tokenResponse", "resultCode": "A242002", "resultMessage": "[A242002] The token request (grant_type=urn:ietf:params:oauth:grant-type:device_code) was processed successfully.", "accessToken": "ZJHO26vXTC1LIQXm9aYUFnMZd4R599aFA4hLBmH-OlM", "action": "OK", "clientId": ..., "grantType": "DEVICE_CODE", "idToken": "eyJhbGciOiJIUzI1NiJ9. eyJhdF9oYXNoIjoiZkpNOHhuODlTaVNQVnNsMGFLYnBTQSIsInN1YiI6InRlc3R1 c2VyMDEiLCJhdWQiOiIxNzIwMTA4MzE2NjE2MSIsImlzcyI6Imh0dHBzOi8vYXV0 aGxldGUuY29tIiwiZXhwIjoxNTk2NjE5OTk2LCJpYXQiOjE1OTY1MzM1OTZ9. OYuGqNbombW_DrSHsm9A07LZWa4UWyV_hSiSAQy-CYI", "refreshToken": "sliwK3Oa6Pag1c2aGenZALcGZXAP9cIiIu_zjGIdBCI", "responseContent": "{\"access_token\":\"ZJHO26vXTC1LIQXm9aYUFnMZd4R599aFA4hLBmH-OlM\", \"refresh_token\":\"sliwK3Oa6Pag1c2aGenZALcGZXAP9cIiIu_zjGIdBCI\", \"scope\":\"openid profile read\", \"id_token\": \"eyJhbGciOiJIUzI1NiJ9. eyJhdF9oYXNoIjoiZkpNOHhuODlTaVNQVnNsMGFLYnBTQSIsInN1YiI6InRlc3R1 c2VyMDEiLCJhdWQiOiIxNzIwMTA4MzE2NjE2MSIsImlzcyI6Imh0dHBzOi8vYXV0 aGxldGUuY29tIiwiZXhwIjoxNTk2NjE5OTk2LCJpYXQiOjE1OTY1MzM1OTZ9. OYuGqNbombW_DrSHsm9A07LZWa4UWyV_hSiSAQy-CYI\", \"token_type\":\"Bearer\", \"expires_in\":3600}", "scopes": [ "openid", "profile", "read" ], "subject": "testuser01", ... }
認可サーバーは “responseContent” の値を抽出し、クライアントに返却します(手順 #d)。