Table of Contents
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 8693 の 4.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 8693 の Section 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 の Token Exchange 対応について解説します。なお同仕様は Authlete 2.3 以降でサポートされます。
クライアントからトークンリクエストを受信した認可サーバーは、そのリクエストの内容を Authlete の /auth/token
API に引数として送信します。同 API は与えられたトークンリクエストに関し、
grant_type
パラメーターの値が urn:ietf:params:oauth:grant-type:token-exchange
であり、かつ場合に、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
リクエストパラメーターで指定されたトークンに関してバリデーションを行います。その際にどのような処理を行うかは、トークンタイプによって異なります。各トークンタイプにおける処理内容を以下に示します。
バリデーション | |
---|---|
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 | 当リフレッシュトークンが当サービスに属していることを確認する。 |
バリデーション | |
---|---|
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 トークンの署名を検証する。 |
バリデーション | |
---|---|
1 | (Authlete はこのトークンタイプに対するバリデーションを行いません。) |
バリデーション | |
---|---|
1 | (Authlete はこのトークンタイプに対するバリデーションを行いません。) |
認可サーバーは、/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) |
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-server の TokenExchanger.java が、トークン交換リクエスト処理の実装サンプルとなっています。 なお当実装は一例に過ぎず、商用利用可能な完璧さは意図していないことにご注意ください。
以下はトークン交換リクエスト・レスポンスの例です。
$ ID_TOKEN=eyJraWQiOiJhdXRobGV0ZS1mYXBpZGV2LWFwaS0yMDE4MDUyNCIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJodHRwczovL2ZhcGlkZXYtYXMuYXV0aGxldGUubmV0LyIsInN1YiI6IjEwMDQiLCJhdWQiOlsiNTg5OTQ2MzYxNDQ0ODA2MyJdLCJleHAiOjE2NTg2NzExMzAsImlhdCI6MTY1ODY3MDgzMCwiYXV0aF90aW1lIjoxNjU4NjcwODMwLCJub25jZSI6IjEyMzQ1Njc4OSIsInZlcmlmaWVkX2NsYWltcyI6eyJ2ZXJpZmljYXRpb24iOnsidHJ1c3RfZnJhbWV3b3JrIjoibmlzdF84MDBfNjNBIn0sImNsYWltcyI6eyJnaXZlbl9uYW1lIjoiSW5nYSIsImZhbWlseV9uYW1lIjoiU2lsdmVyc3RvbmUiLCJiaXJ0aGRhdGUiOiIxOTkxLTExLTA2IiwiOmFnZV8xOF9vcl9vdmVyIjpudWxsfX19.mxE8FQaDb0edY_rWasSQ7pEMXbFon7oWr-Ccv1dB15q8eh2MaKRGrgvwPw_XjAdXlMNzkcV6iEUjRUvLGTvzm7_45cdoOxRX1xWzQw-vwvRbM46xd3Yht3EVjyRUUBJ_92J1yBmu7Nn93rygcnCE-fC_bSTSIJWgEnoC7dpxHYnoJ2QHrIOYFMBAA_3ZYCLGpgiWbIZnB2D1ib2eqwJ9zoJqeFNEBhXo9ThYkASHYaG-ZWofy7364lgeV4Rqy1r4XqzchFRW4yzWs_IM72bTtXTUkstlNOxZU12KEz50uVhtcOXv06iI71I9vceRP-ZVICpq7Knt0vEKWTM41E3ziw
$ 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
{
"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"
}