ハイブリッドフローによる 2 つのアクセストークンの発行

ハイブリッドフローによる 2 つのアクセストークンの発行

はじめに

OpenID Connect の「ハイブリッドフロー」では、単一のクライアントに対して、1 回の認可フローによって 2 つのアクセストークンを発行 できます。

本記事では、このフローによる、スコープや有効期限を制限したアクセストークン発行のユースケースと、Authlete による処理手順を説明します。

ユースケース

ハイブリッドフローは、たとえば「モバイル端末側のネイティブアプリケーション」と「Web サーバー側のバックエンドアプリケーション」から構成されるクライアントに対してアクセストークンを発行する際に有用です。

ネイティブアプリケーションは多くの場合パブリッククライアントであり、そのセキュリティリスクは Web サーバーアプリケーションのようなコンフィデンシャルクライアントに比較して高くなります。このような場合にハイブリッドフローを用いて、それぞれの側のアプリケーションに対してアクセストークンを発行し、かつネイティブアプリケーション向けにはスコープを制限(サブセット化)したアクセストークンとすることが、リスクの低減に有用です。

さらに、比較的高リスクな API にアクセスする Web サーバーアプリケーションに対しては、発行するアクセストークンの有効期限をネイティブアプリケーション向けに比較して短くすることも、ハイブリッドフローによって可能となります。

Authlete における対応

Authlete はハイブリッドフローをサポートしています。たとえば “response_type=code token” が指定された認可リクエストに対しては、Authlete の /auth/authorization/issue API は認可コードとアクセストークンを含む認可レスポンスを生成し、かつその認可コードが指定されたトークンリクエストに対しては、/auth/token API は別のアクセストークンを含むトークンレスポンスを生成します。

また Authlete のトークン更新 API (/auth/token/update API) を用いると、発行されたアクセストークンについて、そのトークンのスコープを狭めたり、有効期限を変えたりといった更新が可能です。これらの更新は Authlete が保持しているアクセストークンに対して行われます。

そして後に、クライアントからアクセストークンを受け取ったリソースサーバーが、そのトークンの状態と詳細を Authlete のイントロスペクション API (/auth/introspection ) に確認すると、上記にて更新した内容が返却されることになります。

hybrid-flow-1_ja

実行例

ここでは、“response_type=code token” を含む認可リクエストに対して、認可サーバーがスコープや有効期限の異なる 2 つのアクセストークンを発行する例を紹介します。

hybrid-flow-2_ja

認可レスポンスから返却するアクセストークンの取得

クライアントから(ユーザーエージェント経由で)認可リクエストを受信した認可サーバー(処理 #1)は、典型的には以下の処理を行います。

  • 認可リクエストの内容を Authlete の /auth/authorization API に送信(処理 #2, #3)
  • ユーザー認証と同意確認の実施(処理 #4, 5)
  • /auth/authorization/issue API を呼び出し、認可レスポンスの内容を取得(処理 #6, 7)

以下は /auth/authorization/issue API のレスポンス例です(処理 #7)。認可エンドポイントからクライアントに返却されるアクセストークンが、“accessToken” の値として含まれており、また “responseContent” の内容から、scope=openid profile payment であるとわかります。(以下、実行例は見やすさのために折り返しています)

{
    "type": "authorizationIssueResponse",
    "resultCode": "A040001",
    "resultMessage": "[A040001] The authorization request was processed successfully.",
    "accessToken": "stm3IfTHV_F1iVKEYQBJA58XMzA7OQ59-MQpdM3NH6c",
    "accessTokenDuration": 1800,
    "accessTokenExpiresAt": 1596379999493,
    "action": "LOCATION",
    "authorizationCode": "iEATjO9V24Rubj1_PnATziCGh4ivVPRcTK_VZOsGplI",
    "responseContent": "https://client.example.org/cb/example.com
                        #code=iEATjO9V24Rubj1_PnATziCGh4ivVPRcTK_VZOsGplI
                        &access_token=stm3IfTHV_F1iVKEYQBJA58XMzA7OQ59-MQpdM3NH6c
                        &token_type=Bearer
                        &expires_in=1800
                        &scope=openid+profile+payment"
}

スコープの制限

発行されたアクセストークンについて、Authlete の /auth/token/update API を用いて、付与されているスコープを変更(付与したくないスコープを削除)します(処理 #6, #7)。ここでは payment スコープを削除し、openid および profile スコープを残します。

JWT ベースのアクセストークン 」を有効化している場合、ここで “accessToken” として指定する値は、先のレスポンスに含まれる “accessToken” です。クライアントに返却する JWT 形式のアクセストークン (“jwtAccessToken”) ではないことにご注意ください。

  • リクエスト(処理 #6. curl による実行例)
curl -s -X POST $apiUrl/auth/token/update \
  -u $apiKey:$apiSecret \
  -H 'Content-type: application/json' \
  -d '{"accessToken":"stm3IfTHV_F1iVKEYQBJA58XMzA7OQ59-MQpdM3NH6c", \
      "scopes":["openid","profile"]}'

  • レスポンス(処理 #7)
{
    "type": "tokenUpdateResponse",
    "resultCode": "A135001",
    "resultMessage": "[A135001] Updated the access token successfully.",
    "accessToken": "stm3IfTHV_F1iVKEYQBJA58XMzA7OQ59-MQpdM3NH6c",
    "accessTokenExpiresAt": 1596379999000,
    "action": "OK",
    "scopes": [
        "openid",
        "profile"
    ],
    "tokenType": "Bearer"
}

認可サーバーはこの後、先の  /auth/authorization/issue API のレスポンスの “responseContent” の内容をクライアントに返却することになります(処理 #8)。

“responseContent” に含まれている scope パラメーターの値は変更前のもの (“scope=openid+profile+payment”) であることに注意してください。また「JWT ベースのアクセストークン 」を有効化している場合にも、アクセストークンに含まれる “scopes” の値は変更前のまま (“scope”: “openid profile payment”) です。

トークンレスポンスから返却するアクセストークンの取得

クライアントからトークンリクエストを受信した認可サーバー(処理 #13)は、典型的には以下の処理を行います。

  • /auth/token API を呼び出し、トークンレスポンスの内容を取得(処理 #14, #15)

以下は /auth/token API のレスポンス例です(処理 #15)。トークンエンドポイントからクライアントに返却されるアクセストークンが、“accessToken” の値として含まれており、また “accessTokenExpiresAt” の値から、アクセストークンの失効日時(Unix 時間)が 1596380052227 であるとわかります。

{
   "type": "tokenResponse",
   "resultCode": "A050001",
   "resultMessage": "[A050001] The token request (grant_type=authorization_code) was processed successfully.",
   "accessToken": "zcxCRPL3P1n7gUnn1gnRAz3nndUdYeyilpU7txGE3gg",
   "accessTokenDuration": 1800,
   "accessTokenExpiresAt": 1596380052227,
   "action": "OK",
   "clientId": 17201083166161,
   "clientIdAlias": "clientapp01",
   "clientIdAliasUsed": false,
   "grantType": "AUTHORIZATION_CODE",
   "idToken": "eyJhbGciOiJIUzI1NiJ9. eyJzdWIiOiJ0ZXN0dXNlcjAxIiwiYXVkIjpbIjE3MjAxMDgzMTY2MTYxIl0s ImlzcyI6Imh0dHBzOi8vYXV0aGxldGUuY29tIiwiZXhwIjoxNTk2NDY0NjUy LCJpYXQiOjE1OTYzNzgyNTJ9. uJiJxISW7d0HEgOyQsIFbqptOREeqs9zJYMs_ZhZ72w",
   "refreshToken": "98YoC89H9CYcc56amo0bvNZ8pV02dYZmxCvB0Wz31jU",
   "refreshTokenDuration": 9000,
   "refreshTokenExpiresAt": 1596387252227,
   "responseContent":
        "{\"access_token\":\"zcxCRPL3P1n7gUnn1gnRAz3nndUdYeyilpU7txGE3gg\",
        \"refresh_token\":\"98YoC89H9CYcc56amo0bvNZ8pV02dYZmxCvB0Wz31jU\",
        \"scope\":\"openid profile payment\",
        \"id_token\":
          \"eyJhbGciOiJIUzI1NiJ9.
            eyJzdWIiOiJ0ZXN0dXNlcjAxIiwiYXVkIjpbIjE3MjAxMDgzMTY2MTYxIl0s
            ImlzcyI6Imh0dHBzOi8vYXV0aGxldGUuY29tIiwiZXhwIjoxNTk2NDY0NjUy
            LCJpYXQiOjE1OTYzNzgyNTJ9.
            uJiJxISW7d0HEgOyQsIFbqptOREeqs9zJYMs_ZhZ72w\",
        \"token_type\":\"Bearer\",
        \"expires_in\":1800}",
   "scopes": [
      "openid",
      "profile",
      "payment"
   ],
   "subject": "testuser01"
}

有効期限の制限

上記の例と同様に、Authlete の /auth/token/update API を用いて、アクセストークンの有効期限を変更します(処理 #16, #17)。ここでは元の失効日時から 900 秒(900,000 ミリ秒)早めて 1596379152000 とします。

  • リクエスト(処理 #16. curl による実行例)
curl -s -X POST $apiUrl/auth/token/update \
    -u $apiKey:$apiSecret \
    -H 'Content-type: application/json' \
    -d '{"accessToken":"zcxCRPL3P1n7gUnn1gnRAz3nndUdYeyilpU7txGE3gg", \
        "accessTokenExpiresAt":1596379152000}'

  • レスポンス(処理 #17)
{
    "type": "tokenUpdateResponse",
    "resultCode": "A135001",
    "resultMessage": "[A135001] Updated the access token successfully.",
    "accessToken": "zcxCRPL3P1n7gUnn1gnRAz3nndUdYeyilpU7txGE3gg",
    "accessTokenExpiresAt": 1596379152000,
    "action": "OK",
    "scopes": [
        "openid",
        "profile",
        "payment"
    ],
    "tokenType": "Bearer"
}