デバイスフローの有効化

デバイスフローの有効化

はじめに

いわゆる「デバイスフロー」は、Web ブラウザ非搭載のデバイスや、文字入力が困難なデバイスが API クライアントとなる場合に、ユーザーの承認に基づいてアクセストークンを発行する認可フローです。RFC 8628 (OAuth 2.0 Device Authorization Grant) によって定義されています。

本記事では、Authlete を用いたデバイスフロー対応認可サーバーの構成と、Authlete の設定手順について説明します。

本機能は Authlete バージョン 2.1 以降にて利用可能です。

Authlete API を用いた認可サーバーのデバイスフロー対応

認可サーバーがデバイスフローに対応するためには、以下のエンドポイントおよび「検証 URI」の、新設・改修が必要です。これらを実装するための機能を、Authlete は API として提供しています。

enabling-device-flow_1

Authlete の設定

本セクションではデバイスフローに対応するための設定を説明します。Authlete サービスと、同フローを用いるクライアントの、両方の設定が必要です。

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
enabling-device-flow_2
enabling-device-flow_3

クライアントの設定

クライアントアプリ開発者コンソールにアクセスし、以下の項目を設定します。

タブ 項目 設定内容
基本情報 クライアントタイプ PUBLIC を選択
認可 認可種別 DEVICE_CODE を有効化
認可 [トークンエンドポイント]クライアント認証方式 NONE を選択

本記事では、デバイスは「コンフィデンシャルではないクライアント 」を想定しています。もしデバイスがコンフィデンシャルクライアントである場合には、「クライアントタイプ」および「クライアント認証方式」についてそれぞれ適切な値を選択してください。

enabling-device-flow_4
enabling-device-flow_5
enabling-device-flow_6

実行例

以下は、クライアントからのデバイス認可リクエストを起点に、エンドユーザーから提示された user_code の検証を行い、クライアントからのトークンリクエストに対して応答する例です。

device-flow-2_ja

デバイス認可リクエスト

ここでは、クライアントが認可サーバーに対して以下の「デバイス認可リクエスト」を送信したとします(手順 #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)。

  • リクエスト(手順 #3。curl コマンドによる実行例)
curl -s -X POST $apiUrl/device/authorization \
   -u $apiKey:$apiSecret \
   -H 'Content-type: application/json' \
   -d '{"parameters": "client\_id=...&scope=openid+profile+read
"}'
  • レスポンス(手順 #4)
{
  "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。詳細は省略)。

「検証 URI」

user_code の検証

クライアントは、認可サーバーから返却されたレスポンスに含まれる “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)。

  • リクエスト(手順 #8。curl コマンドによる実行例)
curl -s -X POST $apiUrl/device/verification \
    -u $apiKey:$apiSecret \
    -H 'Content-type: application/json' \
    -d '{"userCode":"TXBBPHDZ"}'
  • レスポンス(手順 #9)
{
    "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)。

  • リクエスト(手順 #12。curl コマンドによる実行例)
curl -s -X POST $apiUrl/device/complete
  -u $apiKey:$apiSecret
  -H 'Content-type: application/json'
  -d '{"userCode":"TXBBPHDZ",
       "result":"AUTHORIZED",
       "subject":"testuser01"}'
  • レスポンス(手順 #13)
{
    "type": "deviceCompleteResponse",
    "resultCode": "A241001",
    "resultMessage": "[A241001] The API call was processed successfully.",
    "action": "SUCCESS"
}

この後認可サーバーは処理が完了した旨をエンドユーザーに示し、検証処理を終了します(手順 #14)。

トークンリクエスト

前述の通りクライアントは、 “device_code” の値を用いて、認可サーバーに対し「デバイスアクセストークンリクエスト」を送信します(手順 #a)。基本的には、user_code の検証が完了してアクセストークンが取得できるまで、繰り返しリクエストを行います(ポーリングします)。

  • リクエスト(手順 #b。curl コマンドによる実行例)
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"}'
  • レスポンス(手順 #c。user_code の検証完了前)
{
  "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\"}",
...
}
  • レスポンス(手順 #c。user_code の検証完了後)
{
  "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)。

関連情報