デバイスフローの有効化

はじめに


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

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





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


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

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

image.png 45.12 KB

image.png 70.81 KB

クライアントの設定


クライアントアプリ開発者コンソールにアクセスし、以下の項目を設定します。
タブ
項目
設定内容
基本情報
クライアントタイプ
PUBLIC を選択
認可
認可種別
DEVICE_CODE を有効化
認可
[トークンエンドポイント]
クライアント認証方式
NONE を選択

実行例


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


デバイス認可リクエスト


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

関連情報


How did we do with this article?