メインコンテンツへスキップ
このページは Authlete 3.0 用です。2.x についてはこちらのページをご覧ください。

はじめに

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 に登録されています。
トークンタイプトークンタイプ識別子
JWTurn: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
JWT 用のトークンタイプ識別子は RFC 7519 JSON Web Token (JWT) で定義されています。他の識別子は RFC 8693 OAuth 2.0 Token Exchange で定義されています。

トークン交換リクエスト

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

グラントタイプ

他のトークンリクエストとトークン交換リクエストを区別するため、新しいグラントタイプ 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 リクエストパラメーターが与えられていない場合、このリクエストパラメーターが存在していてはいけません。
RFC 6749 The OAuth 2.0 Authorization Framework は “Request and response parameters MUST NOT be included more than once.” (リクエストパラメーターとレスポンスパラメーターは複数回含まれていてはならない) と述べています。resource リクエストパラメーターと audience リクエストパラメーターは例外です。

トークン交換レスポンス

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

レスポンスパラメーター

トークン交換レスポンスに関連するパラメーターを以下に示します。
レスポンスパラメーター要否説明
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 は次の処理を行います。
番号バリデーション
1requested_token_type リクエストパラメーターが与えられていて、その値が空でない場合、その値が登録されているトークンタイプ識別子のいずれかであることを確認する。
2subject_token リクエストパラメーターが与えられていること、その値が空でないことを確認する。
3subject_token_type リクエストパラメーターが与えらていること、その値が登録されているトークンタイプ識別子のいずれかであることを確認する。
4actor_token_type リクエストパラメーターが与えられていて、その値が空でない場合、その値が登録されているトークンタイプ識別子のいずれかであることを確認する。
5actor_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 の署名は検証されない。)
JWT の復号化に使う鍵を取得する標準的な方法が存在しないため、Authlete は暗号化された JWT のバリデーションをおこないません。これは、暗号化された JWT がトークンタイプ urn:ietf:params:oauth:token-type:jwt の入力トークンとして用いられた場合、(サービスの tokenExchangeEncryptedJwtRejected フラグが false の場合) あなた自身で暗号化された JWT のバリデーションを実施しなければならないということを意味します。
JWT の署名検証に使う鍵を取得する標準的な方法が存在しないため、Authlete は JWT の署名を検証しません。これは、署名された JWT がトークンタイプ urn:ietf:params:oauth:token-type:jwt の入力トークンとして用いられた場合、あなた自身で JWT の署名を検証しなければならないということを意味します。

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

番号バリデーション
1トークンが、あなたのサービスの Authlete サーバーにより発行されたアクセストークンであることを確認する。
2当アクセストークンが有効期限切れしていないことを確認する。
3当アクセストークンが当サービスに属していることを確認する。
上述のバリデーション処理は、他のシステムが発行したアクセストークンを、トークンタイプ urn:ietf:params:oauth:token-type:access_token のサブジェクトトークンやアクタートークンとして用いることができないことを示唆しています。当バリデーション処理を無効化するためのオプションを Authlete は将来提供するかもしれませんが、本記事執筆時点ではその計画はありません。

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

番号バリデーション
1トークンが、あなたのサービスの Authlete サーバーにより発行されたリフレッシュトークンであることを確認する。
2当リフレッシュトークンが有効期限切れしていないことを確認する。
3当リフレッシュトークンが当サービスに属していることを確認する。
上述のバリデーション処理は、他のシステムが発行したリフレッシュトークンを、トークンタイプ urn:ietf:params:oauth:token-type:refresh_token のサブジェクトトークンやアクタートークンとして用いることができないことを示唆しています。当バリデーション処理を無効化するためのオプションを Authlete は将来提供するかもしれませんが、本記事執筆時点ではその計画はありません。

トークンタイプ: 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 トークンの署名を検証する。
暗号化された ID トークン用のクライアント ID を決定することができないトークン交換の文脈においては、ID トークンの復号化に使う鍵を取得する標準的な方法が存在しないため、Authlete は暗号化された ID トークンのバリデーションをおこないません。これは、暗号化された ID トークンがトークンタイプ urn:ietf:params:oauth:token-type:id_token の入力トークンとして用いられた場合、(サービスの tokenExchangeEncryptedJwtRejected フラグが false の場合) あなた自身で暗号化された ID トークンのバリデーションを実施しなければならないということを意味します。
上述の 10 番目のバリデーション処理は、署名アルゴリズムが対称鍵系 (HS256HS384 または HS512) の ID トークンをトークンタイプ urn:ietf:params:oauth:token-type:id_token のサブジェクトトークンやアクタートークンとして用いることができないことを示唆しています。トークン交換の文脈においては、署名検証に使う対称鍵 (つまりクライアントシークレット) を決定できないからです。
ID トークンの発行者があなたのサービスではない場合でも署名検証は実行されます。ただしその場合、発行者は OpenID Connect Discovery 1.0 で定義されているディスカバリーエンドポイントをサポートしなければなりません。サポートしていない場合、署名検証は失敗します。

トークンタイプ: 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"
}