RFC 8693 OAuth 2.0 Token Exchange

はじめに

RFC 8693 OAuth 2.0 Token Exchange (以下 “Token Exchange”) は、トークンエンドポイントに既存のトークンを提示して新しいトークンを取得する方法を定義している技術仕様です。

当仕様はとても柔軟ですが、実のところ、安全なトークン交換に必要となる詳細事項については定義されていません。そのため、同仕様を認可サーバーに実装する際には、未定義部分に関する詳細仕様化が必要です。

本記事では、Token Exchange 仕様を用いる場合の詳細仕様化の要点と、Authlete における同仕様のサポートについて説明します。

仕様

次の図はトークン交換フローを示しています。

以下のセクションでは、トークンのタイプ、そしてトークン交換リクエスト・レスポンスについて述べます。

トークンの種別

トークン交換フローにおいて入力として提示するトークンには、サブジェクトトークン (Subject Token) とアクタートークン (Actor Token) の 2 種類があります。

サブジェクトトークン

サブジェクトトークンは提示が必須のトークンです。RFC 8693 では以下のように定義されています。

( RFC 8693 Section 2.1. Request より抜粋 )
A subject token represents the identity of the party on behalf of whom the request is being made.

サブジェクトトークンは、リクエストの当事者のアイデンティティを表すものである。

つまり、サブジェクトトークンにより特定されるサブジェクトが、新しく発行されるトークンのサブジェクトとして用いられることが想定されています。

アクタートークン

アクタートークンは任意に提示するトークンです。RFC 8693 では以下のように定義されています。

( RFC 8693 Section 2.1. Request より抜粋 )
An actor token represents the identity of the acting party.

アクタートークンは、代行者のアイデンティティを表すものである。

新しく発行されたトークンのサブジェクトと、そのトークンを利用する代行者とを区別する必要がある場合、認可サーバーはアクタートークンを活用し、発行するトークンに代行者に関する情報を埋め込んでもよいでしょう。RFC 86934.1 節4.4 節は、代行者に関連する JWT クレームを定義しています。

トークンタイプ

これらのトークンが取りうる型として、仕様は次のトークンタイプを列挙し、それぞれにトークンタイプ識別子を割り当てています。これらのトークンタイプ識別子は IANA (Internet Assigned Numbers Authority) の OAuth Parameters / OAuth URI に登録されています。

トークンタイプ トークンタイプ識別子
JWT urn:ietf:params:oauth:token-type:jwt
アクセストークン urn:ietf:params:oauth:token-type:access_token
リフレッシュトークン urn:ietf:params:oauth:token-type:refresh_token
ID トークン urn:ietf:params:oauth:token-type:id_token
SAML 1.1 アサーション urn:ietf:params:oauth:token-type:saml1
SAML 2.0 アサーション urn:ietf:params:oauth:token-type:saml2

トークン交換リクエスト

トークン交換リクエストはトークンリクエストの一種です。

グラントタイプ

他のトークンリクエストとトークン交換リクエストを区別するため、新しいグラントタイプ urn:ietf:params:oauth:grant-type:token-exchange が仕様で定義されています。この値はトークンリクエストの grant_type リクエストパラメーターの値として使用されます。

クライアントの特定と認証

仕様自体は、トークンエンドポイントにおけるクライアント認証やクライアントの特定を要求していません。これについて RFC 8693Section 2.1. Request は次のように述べています。

The supported methods of client authentication and whether or not to allow unauthenticated or unidentified clients are deployment decisions that are at the discretion of the authorization server.

サポートするクライアント認証方式や、未認証または未特定のクライアントを許容するかどうかは、認可サーバーの自由裁量の範疇で運用方針に応じて決定する事項である。

技術的には、『未認証のクライアント』はパブリッククライアントを意味し (参照: RFC 6749 Section 2.1. Client Types)、『未特定のクライアント』はトークンリクエストがクライアントを特定するための情報を含んでいないことを意味します。

RFC 8693付録 1.1 には特定不能なクライアントからのトークン交換リクエストの例が示されていますが、このようなケースは、サービス内の閉じた環境のような、特定の条件下に限られます。それ以外の場合には、安全なトークン交換のための詳細仕様化を行い、RFC 8693 を補完する必要があります。

リクエストパラメーター

トークン交換リクエストに関連するパラメーターを以下に示します。

リクエストパラメーター 要否 説明
grant_type 必須 urn:ietf:params:oauth:grant-type:token-exchange という値によりトークンリクエストがトークン交換リクエストであることが示されます。
resource 任意 新しく発行されるトークンに結び付けられるリソース。このリクエストパラメーターは複数回指定してもよいとされています。詳細は RFC 8707 Resource Indicators for OAuth 2.0 を参照してください。
audience 任意 新しく発行されるトークンの対象。このリクエストパラメーターも複数回指定してもよいとされています。
scope 任意 新しく発行されるトークンに結び付けられるスコープ群の名前をスペース区切りで並べたもの。
requested_token_type 任意 クライアントが希望する、新しく発行されるトークンのトークンタイプ。登録されているトークンタイプ識別子のいずれか。
subject_token 必須 サブジェクトトークンの値。
subject_token_type 必須 サブジェクトトークンのトークンタイプ。登録されているトークンタイプ識別子のいずれか。
actor_token 任意 アクタートークンの値。
actor_token_type 任意 アクタートークンのトークンタイプ。登録されているトークンタイプ識別子のいずれか。actor_token リクエストパラメーターが与えられている場合、このリクエストパラメーターは必須。逆に、actor_token リクエストパラメーターが与えられていない場合、このリクエストパラメーターが存在していてはいけません。

トークン交換レスポンス

トークン交換レスポンスはトークンレスポンスの一種です。

レスポンスパラメーター

トークン交換レスポンスに関連するパラメーターを以下に示します。

レスポンスパラメーター 要否 説明
access_token 必須 トークンタイプに関わらず、新しく発行されたトークンの値はこのレスポンスパラメーターにセットされます。
issued_token_type 必須 新しく発行されたトークンのトークンタイプ。登録されているトークンタイプ識別子のいずれか。
token_type 必須 新しく発行されたトークンのトークンタイプが urn:ietf:params:oauth:token-type:access_token のとき、このレスポンスパラメーターは RFC 6749 The OAuth 2.0 Authorization Framework で定義されているものと同じ意味を持ちます。それ以外の場合は "N_A" がセットされます。
expires_in 任意 新しく発行されたトークンの有効時間 (秒単位)。
scope 任意 新しく発行されたトークンに結び付けられたスコープ群。新しく発行されたトークンに結び付けられたスコープ群の実際の組がトークン交換リクエストで要求された組と異なる場合、このレスポンスパラメーターは必須。
refresh_token 任意 リフレッシュトークン (参照: RFC 6749 Section 6. Refreshing an Access Token)。

Authlete における対応

本セクションでは、Authlete の Token Exchange 対応について解説します。なお同仕様は Authlete 2.3 以降でサポートされます。

トークン交換リクエスト

/auth/token API のアクション値

クライアントからトークンリクエストを受信した認可サーバーは、そのリクエストの内容を Authlete の /auth/token API に引数として送信します。同 API は与えられたトークンリクエストに関し、

  • grant_type パラメーターの値が urn:ietf:params:oauth:grant-type:token-exchange であり、かつ
  • そのリクエストが Authlete サーバー側で実行される基本的なバリデーション処理をパスした

場合に、action パラメーターの値として TOKEN_EXCHANGE を含むレスポンスを返却します。

その他、/auth/token API からのレスポンスに含まれるトークン交換関連のパラメーターを、次の表に示します。

レスポンスパラメーター 説明
resources 文字列配列 resource リクエストパラメーター群の値。
audiences 文字列配列 audience リクエストパラメーター群の値。
scopes 文字列配列 scope リクエストパラメーターに列挙されたスコープ群。
requestedTokenType 文字列 requested_token_type リクエストパラメーターの値を示す文字列。 "ACCESS_TOKEN" など。(参照: TokenType.java)
subjectToken 文字列 subject_token リクエストパラメーターの値。
subjectTokenType 文字列 subject_token_type リクエストパラメーターの値を示す文字列。 "ACCESS_TOKEN" など。(参照: TokenType.java)
subjectTokenInfo オブジェクト サブジェクトトークンに関する情報。トークンタイプがアクセストークンかリフレッシュトークンの場合のみ利用可能。
actorToken 文字列 actor_token リクエストパラメーターの値。
actorTokenType 文字列 actor_token_type リクエストパラメーターの値を示す文字列。 "ACCESS_TOKEN" など。(参照: TokenType.java)
actorTokenInfo オブジェクト アクタートークンに関する情報。トークンタイプがアクセストークンかリフレッシュトークンの場合のみ利用可能。

トークンのバリデーション

入力トークンのバリデーションについて、RFC 8693 には次のように記述されています。

In processing the request, the authorization server MUST perform the appropriate validation procedures for the indicated token type and, if the actor token is present, also perform the appropriate validation procedures for its indicated token type. The validity criteria and details of any particular token are beyond the scope of this document and are specific to the respective type of token and its content.

つまり、適切なバリデーションが必須である (MUST) とされていますが、その基準や手順については RFC 8693 では定められておらず、この仕様を実装する認可サーバーに委ねられています。そのようなバリデーション実装の負担を軽減するため、Authlete の /auth/token API は次の処理を行います。

番号 バリデーション
1 requested_token_type リクエストパラメーターが与えられていて、その値が空でない場合、その値が登録されているトークンタイプ識別子のいずれかであることを確認する。
2 subject_token リクエストパラメーターが与えられていること、その値が空でないことを確認する。
3 subject_token_type リクエストパラメーターが与えらていること、その値が登録されているトークンタイプ識別子のいずれかであることを確認する。
4 actor_token_type リクエストパラメーターが与えられていて、その値が空でない場合、その値が登録されているトークンタイプ識別子のいずれかであることを確認する。
5 actor_token リクエストパラメーターが与えられていないか、その値が空の場合、actor_token_type リクエストパラメーターが与えられていない、もしくはその値が空であることを確認する。

Authlete はさらに、トークン交換リクエストに含まれる、subject_token リクエストパラメーターと actor_token リクエストパラメーターで指定されたトークンに関してバリデーションを行います。その際にどのような処理を行うかは、トークンタイプによって異なります。各トークンタイプにおける処理内容を以下に示します。

トークンタイプ: JWT

番号 バリデーション
1 フォーマットが JWT 仕様 (RFC 7519 JSON Web Token (JWT)) に従っていることを確認する。
2 当 JWT が暗号化されている場合、(a) サービスの tokenExchangeEncryptedJwtRejected フラグが true であればトークン交換リクエストを拒否し、(b) 当フラグが false であれば以降のバリデーション処理をスキップする。
3 当 JWT が exp クレームを含んでいる場合、現在時刻が当クレームで示される時刻に達していないことを確認する。
4 当 JWT が iat クレームを含んでいる場合、現在時刻が当クレームで示される時刻以降であることを確認する。
5 当 JWT が nbf クレームを含んでいる場合、現在時刻が当クレームで示される時刻以降であることを確認する。
6 当 JWT が無署名の場合、(a) サービスの tokenExchangeUnsignedJwtRejected フラグが true であればトークン交換リクエストを拒否し、(b) 当フラグが false であれば入力トークンに対するバリデーション処理を終了する。
7 (当 JWT の署名は検証されない。)

トークンタイプ: アクセストークン

番号 バリデーション
1 トークンが、あなたのサービスの Authlete サーバーにより発行されたアクセストークンであることを確認する。
2 当アクセストークンが有効期限切れしていないことを確認する。
3 当アクセストークンが当サービスに属していることを確認する。

トークンタイプ: リフレッシュトークン

番号 バリデーション
1 トークンが、あなたのサービスの Authlete サーバーにより発行されたリフレッシュトークンであることを確認する。
2 当リフレッシュトークンが有効期限切れしていないことを確認する。
3 当リフレッシュトークンが当サービスに属していることを確認する。

トークンタイプ: ID トークン

番号 バリデーション
1 フォーマットが JWT 仕様 (RFC 7519 JSON Web Token (JWT)) に従っていることを確認する。
2 当 ID トークンが暗号化されている場合、(a) サービスの tokenExchangeEncryptedJwtRejected フラグが true であればトークン交換リクエストを拒否し、(b) 当フラグが false であれば以降のバリデーション処理をスキップする。
3 当 ID トークンが exp クレームを含んでおり、現在時刻が当クレームで示される時刻に達していないことを確認する。
4 当 ID トークンが iat クレームを含んでおり、現在時刻が当クレームで示される時刻以降であることを確認する。
5 当 ID トークンが nbf クレームを含んでいる場合、現在時刻が当クレームで示される時刻以降であることを確認する。
6 当 ID トークンが iss クレームを含んでおり、その値が有効な URI であることを確認する。加えて、当 URI が https スキームを持つこと、クエリー部を持たないこと、フラグメント部を持たないことを確認する。
7 当 ID トークンが aud クレームを含んでおり、その値が JSON 文字列もしくは JSON 文字列の配列であることを確認する。
8 当 ID トークンが nonce クレームを含んでいる場合、その値が JSON 文字列であることを確認する。
9 当 ID トークンが無署名の場合、(a) サービスの tokenExchangeUnsignedJwtRejected フラグが true であればトークン交換リクエストを拒否し、(b) 当フラグが false であれば入力トークンに対するバリデーション処理を終了する。
10 当 ID トークンの署名アルゴリズムが非対称鍵系であることを確認する。
11 当 ID トークンの署名を検証する。

トークンタイプ: SAML 1.1 アサーション

番号 バリデーション
1 (Authlete はこのトークンタイプに対するバリデーションを行いません。)

トークンタイプ: SAML 2.0 アサーション

番号 バリデーション
1 (Authlete はこのトークンタイプに対するバリデーションを行いません。)

トークン交換レスポンス

/auth/token/create API

認可サーバーは、/auth/token API からのレスポンスに基づいて必要なバリデーションを行い、どのようなトークンを発行するかを決定した後に、トークン交換レスポンスをクライアントに返却します。

トークン交換レスポンスの生成には Authlete の /auth/token/create API を用います。その際、この API のリクエストパラメーターのうち、grantType の値に TOKEN_EXCHANGE を指定します。

関連する設定項目

既に述べたように、安全なトークン交換のためには、詳細仕様化を行い RFC 8693 を補完しなければなりません。その実装を容易にするため、Authlete は次に示す設定項目を提供します。Authlete を用いて認可サーバーを構築する際にご活用ください。

サービス設定

設定項目 説明
特定可能なクライアントのみ 真偽値 特定不能クライアントによるトークン交換リクエストを拒否するか否か - Service.tokenExchangeByIdentifiableClientsOnly (JavaDoc)
コンフィデンシャルクライアントのみ 真偽値 パブリッククライアントによるトークン交換リクエストを拒否するか否か - Service.tokenExchangeByConfidentialClientsOnly (JavaDoc)
許可されたクライアントのみ 真偽値 明示的な許可を持たないクライアントによるトークン交換リクエストを拒否するか否か - Service.tokenExchangeByPermittedClientsOnly (JavaDoc)
暗号化された JWT を拒否 真偽値 暗号化された JWT を入力トークンとして使うトークン交換リクエストを拒否するか否か - Service.tokenExchangeEncryptedJwtRejected (JavaDoc)
無署名の JWT を拒否 真偽値 無署名の JWT を入力トークンとして使うトークン交換リクエストを拒否するか否か - Service.tokenExchangeUnsignedJwtRejected (JavaDoc)

クライアント拡張設定

設定項目 説明
トークン交換の明示的許可 真偽値 トークン交換の明示的許可を持つか否か - ClientExtension.tokenExchangePermitted (JavaDoc)

利用例

認可サーバー実装の例

TOKEN_EXCHANGE アクションの処理

Authlete を用いた認可サーバー実装がトークン交換をサポートするためには、 TOKEN_EXCHANGE アクションを処理しなければなりません。次に示す switch 文は authlete-java-jaxrs ライブラリの TokenRequestHandler.java から抜粋したもので、TOKEN_EXCHANGE アクション処理の一つの例です。当 switch 文には TOKEN_EXCHANGE 用の case エントリーがあります。

// Dispatch according to the action.
switch (action)
{
    case INVALID_CLIENT:
        // 401 Unauthorized
        return ResponseUtil.unauthorized(content, CHALLENGE);

    case INTERNAL_SERVER_ERROR:
        // 500 Internal Server Error
        return ResponseUtil.internalServerError(content);

    case BAD_REQUEST:
        // 400 Bad Request
        return ResponseUtil.badRequest(content);

    case PASSWORD:
        // Process the token request whose flow is "Resource Owner Password Credentials".
        return handlePassword(response);

    case OK:
        // 200 OK
        return ResponseUtil.ok(content);

    case TOKEN_EXCHANGE:
        // Process the token exchange request (RFC 8693)
        return handleTokenExchange(response);

    case JWT_BEARER:
        // Process the token request which uses the grant type
        // urn:ietf:params:oauth:grant-type:jwt-bearer (RFC 7523).
        return handleJwtBearer(response);

    default:
        // This never happens.
        throw getApiCaller().unknownAction("/api/auth/token", action);
}

トークン交換リクエスト処理

Java で書かれたオープンソースの認可サーバー実装サンプルである java-oauth-serverTokenExchanger.java が、トークン交換リクエスト処理の実装サンプルとなっています。 なお当実装は一例に過ぎず、商用利用可能な完璧さは意図していないことにご注意ください。

リクエストとレスポンスの例

以下はトークン交換リクエスト・レスポンスの例です。

1. 何らかの方法で ID トークンを用意する。

$ ID_TOKEN=eyJraWQiOiJhdXRobGV0ZS1mYXBpZGV2LWFwaS0yMDE4MDUyNCIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJodHRwczovL2ZhcGlkZXYtYXMuYXV0aGxldGUubmV0LyIsInN1YiI6IjEwMDQiLCJhdWQiOlsiNTg5OTQ2MzYxNDQ0ODA2MyJdLCJleHAiOjE2NTg2NzExMzAsImlhdCI6MTY1ODY3MDgzMCwiYXV0aF90aW1lIjoxNjU4NjcwODMwLCJub25jZSI6IjEyMzQ1Njc4OSIsInZlcmlmaWVkX2NsYWltcyI6eyJ2ZXJpZmljYXRpb24iOnsidHJ1c3RfZnJhbWV3b3JrIjoibmlzdF84MDBfNjNBIn0sImNsYWltcyI6eyJnaXZlbl9uYW1lIjoiSW5nYSIsImZhbWlseV9uYW1lIjoiU2lsdmVyc3RvbmUiLCJiaXJ0aGRhdGUiOiIxOTkxLTExLTA2IiwiOmFnZV8xOF9vcl9vdmVyIjpudWxsfX19.mxE8FQaDb0edY_rWasSQ7pEMXbFon7oWr-Ccv1dB15q8eh2MaKRGrgvwPw_XjAdXlMNzkcV6iEUjRUvLGTvzm7_45cdoOxRX1xWzQw-vwvRbM46xd3Yht3EVjyRUUBJ_92J1yBmu7Nn93rygcnCE-fC_bSTSIJWgEnoC7dpxHYnoJ2QHrIOYFMBAA_3ZYCLGpgiWbIZnB2D1ib2eqwJ9zoJqeFNEBhXo9ThYkASHYaG-ZWofy7364lgeV4Rqy1r4XqzchFRW4yzWs_IM72bTtXTUkstlNOxZU12KEz50uVhtcOXv06iI71I9vceRP-ZVICpq7Knt0vEKWTM41E3ziw

2. トークン交換リクエストを行う。

$ curl http://localhost:8080/api/token -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange -d subject_token=$ID_TOKEN -d subject_token_type=urn:ietf:params:oauth:token-type:id_token -d client_id=5908895171 -d scope=email

3. トークン交換レスポンスを受け取る。

{
  "access_token":"NEdL-q9EfOI4S5XzaMeimXAXVqS139Jm9DTYeLUAd5o",
  "issued_token_type":"urn:ietf:params:oauth:token-type:access_token",
  "token_type":"Bearer",
  "expires_in":86400,
  "scope":"email",
  "refresh_token":"pK9f5OWIrx58_XWrE_SpCLLN-BM673ljliTSffjqwao"
}