Table of Contents
RFC 8693 OAuth 2.0 Token Exchange(以下、「トークン交換」)は、既存のトークンを提示し、オプションとして追加のトークンを含めることで、新しいトークンを取得する方法を定義する技術標準です。
この仕様は非常に柔軟ですが、その反面、安全なトークン交換のために必要な詳細な要件を定義していません。そのため、認可サーバーでこの仕様を実装する際には、定義されていない部分について追加の検討が必要です。
本記事では、トークン交換仕様を実装するための主要なポイントと、Authlete におけるこの仕様のサポートについて説明します。
以下の図は、トークン交換のフローを示しています。
次のセクションでは、トークンの種類とトークン交換リクエスト/レスポンスについて説明します。
入力トークンには、サブジェクトトークンとアクタートークンの 2 種類があります。
「サブジェクトトークン」は必須の入力トークンです。これは、「リクエストが行われる主体のアイデンティティを表す。」(RFC 8693 のセクション 2.1. リクエストより抜粋)。
この仕様では、サブジェクトトークンによって識別される主体が、新しく発行されるトークンのサブジェクトとして使用されることを想定しています。
「アクタートークン」はオプションの入力トークンです。これは、「行為者のアイデンティティを表す。」(RFC 8693 のセクション 2.1. リクエストより抜粋)。
新しく発行されるトークンのサブジェクトと、トークンを使用する行為者を区別する必要がある場合、認可サーバーの実装ではアクタートークンを活用し、トークン内に行為者に関する情報を埋め込むことができます。RFC 8693 のセクション 4.1およびセクション 4.4では、行為者に関連する JWT クレームが定義されています。
この仕様では、以下のトークンタイプを定義し、それぞれにトークンタイプ識別子を割り当てています。トークンタイプの識別子は、IANA(Internet Assigned Numbers Authority)のOAuth パラメータの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 のセクション 2.1. リクエストでは、次のように述べられています。
クライアント認証のサポート方法や、認証されていない または 識別されていない クライアントを許可するかどうかは、認可サーバーの裁量に委ねられるデプロイメントの決定事項です。
技術的に言うと、「認証されていないクライアント」はパブリッククライアント(RFC 6749 のセクション 2.1. クライアントタイプを参照)を指し、「識別されていないクライアント」とは、リクエストを行うクライアントを識別する情報がトークンリクエストに含まれていないことを意味します。
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 パラメータが指定されている場合に必須。それ以外の場合は存在してはなりません。 |
トークン交換レスポンスは、トークンレスポンスの一種です。
以下の表は、トークン交換レスポンスに関連するパラメータを示しています。
レスポンスパラメータ | 必須 | 説明 |
---|---|---|
access_token |
必須 | トークンの種類に関係なく、新たに発行されたトークンの値がこのレスポンスパラメータに設定されます。 |
issued_token_type |
必須 | 新たに発行されたトークンの種類。登録済みのトークンタイプ識別子のいずれか。 |
token_type |
必須 | 新たに発行されたトークンの種類が urn:ietf:params:oauth:token-type:access_token の場合、このパラメータの意味は RFC 6749 に記載されたものと同じです。それ以外の場合、"N_A" が設定されます。 |
expires_in |
任意 | 新たに発行されたトークンの有効期限(秒単位)。 |
scope |
任意 | 新たに発行されたトークンに関連付けられたスコープ。トークン交換リクエストで要求されたスコープのセットと実際に発行されたトークンのスコープのセットが異なる場合、このパラメータは必須となります。 |
refresh_token |
任意 | リフレッシュトークン(RFC 6749 セクション 6 を参照)。 |
このセクションでは、Authlete におけるトークン交換のサポートについて説明します。
RFC 8693 は Authlete 2.3 以降でサポートされています。
"action"
値クライアントからのトークンリクエストを受信すると、認可サーバーはリクエスト内容をリクエストパラメータの一つとして Authlete の /auth/token
API に送信します。この API は、以下の条件を満たす場合に、レスポンスの action
パラメータの値として TOKEN_EXCHANGE
を返します。
grant_type
リクエストパラメータの値が urn:ietf:params:oauth:grant-type:token-exchange
である以下の表は、/auth/token
API のレスポンスに含まれるトークン交換に関連するパラメータを示しています。
レスポンスパラメータ | 型 | 説明 |
---|---|---|
resources |
文字列配列 | resource リクエストパラメータの値。 |
audiences |
文字列配列 | audience リクエストパラメータの値。 |
scopes |
文字列配列 | scope リクエストパラメータに記載されたスコープ。 |
requestedTokenType |
文字列 | requested_token_type リクエストパラメータの値(例: "ACCESS_TOKEN" )。(cf. TokenType.java) |
subjectToken |
文字列 | subject_token リクエストパラメータの値。 |
subjectTokenType |
文字列 | subject_token_type リクエストパラメータの値(例: "ACCESS_TOKEN" )。(cf. TokenType.java) |
subjectTokenInfo |
オブジェクト | サブジェクトトークンに関する情報。トークンタイプがアクセストークンまたはリフレッシュトークンの場合のみ利用可能。 |
actorToken |
文字列 | actor_token リクエストパラメータの値。 |
actorTokenType |
文字列 | actor_token_type リクエストパラメータの値(例: "ACCESS_TOKEN" )。(cf. TokenType.java) |
actorTokenInfo |
オブジェクト | アクタートークンに関する情報。トークンタイプがアクセストークンまたはリフレッシュトークンの場合のみ利用可能。 |
次のセクションでは、Authlete におけるトークン検証について詳しく説明します。
入力トークンの検証に関して、仕様では以下のように記載されています。
認可サーバーは、指定されたトークンタイプに応じた適切な検証手続きを実行しなければならない。また、アクタートークンが存在する場合は、それに対しても適切な検証を行わなければならない。各トークンの有効性基準や詳細は本ドキュメントの範囲外であり、それぞれのトークンタイプおよびその内容に特有のものである。
これは、認可サーバーが適切な検証を実施する義務を負うことを意味しますが、RFC 8693 では具体的な検証手順を定義していません。そのため、認可サーバーの負担を軽減するために、Authlete の /auth/token
API では以下の検証を行います。
No. | 検証内容 |
---|---|
1 | requested_token_type パラメータの値が指定された場合、それが登録済みのトークンタイプ識別子のいずれかであることを確認する。 |
2 | subject_token パラメータが存在し、その値が空でないことを確認する。 |
3 | subject_token_type パラメータが存在し、その値が登録済みのトークンタイプ識別子のいずれかであることを確認する。 |
4 | actor_token パラメータが指定されている場合、actor_token_type パラメータが存在し、その値が登録済みのトークンタイプ識別子のいずれかであることを確認する。 |
5 | actor_token パラメータが指定されていない場合、actor_token_type パラメータが存在しないか空であることを確認する。 |
さらに、Authlete は subject_token
および actor_token
の値に対して、それぞれのトークンタイプに応じた追加の検証を行います。
No. | 検証内容 |
---|---|
1 | JWT のフォーマットが RFC 7519 JSON Web Token (JWT) に準拠していることを確認する。 |
2 | JWT が暗号化されている場合、(a) サービスの tokenExchangeEncryptedJwtRejected フラグが true であればリクエストを拒否し、(b) false であれば検証をスキップする。 |
3 | exp クレームが含まれている場合、現在時刻が exp より前であることを確認する。 |
4 | iat クレームが含まれている場合、現在時刻が iat 以降であることを確認する。 |
5 | nbf クレームが含まれている場合、現在時刻が nbf 以降であることを確認する。 |
6 | JWT が署名されていない場合、(a) サービスの tokenExchangeUnsignedJwtRejected フラグが true であればリクエストを拒否し、(b) false であれば検証をスキップする。 |
7 | JWT の署名の検証は行わない。 |
No. | 検証内容 |
---|---|
1 | トークンが、サービスの Authlete サーバーによって発行されたアクセストークンであることを確認する。 |
2 | アクセストークンが有効期限切れでないことを確認する。 |
3 | アクセストークンが、現在のサービスに属していることを確認する。 |
No. | 検証内容 |
---|---|
1 | トークンが、サービスの Authlete サーバーによって発行されたリフレッシュトークンであることを確認する。 |
2 | リフレッシュトークンが有効期限切れでないことを確認する。 |
3 | リフレッシュトークンが、現在のサービスに属していることを確認する。 |
No. | 検証内容 |
---|---|
1 | ID トークンのフォーマットが RFC 7519 JSON Web Token (JWT) に準拠していることを確認する。 |
2 | ID トークンが暗号化されている場合、(a) tokenExchangeEncryptedJwtRejected フラグが true であればリクエストを拒否し、(b) false であれば検証をスキップする。 |
3 | exp クレームが含まれており、現在時刻が exp より前であることを確認する。 |
4 | iat クレームが含まれており、現在時刻が iat 以降であることを確認する。 |
5 | nbf クレームが含まれている場合、現在時刻が nbf 以降であることを確認する。 |
6 | iss クレームの値が有効な URI であることを確認し、HTTPS スキームを持ち、クエリやフラグメントを含まないことを検証する。 |
7 | aud クレームの値が文字列または文字列の配列であることを確認する。 |
8 | nonce クレームの値が文字列であることを確認する(存在する場合)。 |
9 | ID トークンが署名されていない場合、(a) tokenExchangeUnsignedJwtRejected フラグが true であればリクエストを拒否し、(b) false であれば検証をスキップする。 |
10 | 署名アルゴリズムが非対称であることを確認する。 |
11 | 署名を検証する。 |
No. | 検証内容 |
---|---|
1 | (Authlete はこのトークンタイプに対する検証を行いません。) |
No. | 検証内容 |
---|---|
1 | (Authlete はこのトークンタイプに対する検証を行いません。) |
認可サーバーは /auth/token
API のレスポンスに基づいて必要な検証を実施し、新たに発行するトークンの種類を決定し、クライアントにトークン交換レスポンスを返します。
認可サーバーは、Authlete の /auth/token/create API を使用してトークン交換レスポンスを作成します。この API に grantType
パラメータの値として TOKEN_EXCHANGE
を指定します。
Authlete でトークン交換を設定するには、以下の手順を実行します。
Authlete 管理コンソール にログインします。
サービス設定に移動します。
エンドポイント > 基本設定 > サポート可能なグラントタイプに移動し、TOKEN_EXCHANGE
グラントを有効にします。
トークン&クレーム > 詳細設定 > トークン交換に移動します。
必要なフラグを有効にして、トークン交換のセキュリティを強化します。
変更を保存をクリックして設定を適用します。
安全なトークン交換を確保するために、RFC 8693 の範囲外で追加の設定を定義する必要があります。Authlete はこの目的のために以下のオプションを提供しています。
設定オプション | タイプ | 説明 |
---|---|---|
識別可能なクライアントのみ | boolean | 識別できないクライアントからのトークン交換リクエストを拒否する。 |
機密クライアントのみ | boolean | パブリッククライアントからのトークン交換リクエストを拒否する。 |
許可されたクライアントのみ | boolean | 明示的な許可を持たないクライアントからのトークン交換リクエストを拒否する。 |
暗号化 JWT の拒否 | boolean | 入力トークンとして暗号化 JWT を使用するトークン交換リクエストを拒否する。 |
署名なし JWT の拒否 | boolean | 入力トークンとして署名なし JWT を使用するトークン交換リクエストを拒否する。 |
認可サーバーの実装では、TOKEN_EXCHANGE
アクションを処理する必要があります。以下の switch
文は、authlete-java-jaxrs ライブラリの TokenRequestHandler.java から抜粋したものです。この switch
文には TOKEN_EXCHANGE
の case
エントリがあります。
// アクションに応じて分岐
switch (action)
{
case INVALID_CLIENT:
return ResponseUtil.unauthorized(content, CHALLENGE);
case INTERNAL_SERVER_ERROR:
return ResponseUtil.internalServerError(content);
case BAD_REQUEST:
return ResponseUtil.badRequest(content);
case PASSWORD:
return handlePassword(response);
case OK:
return ResponseUtil.ok(content);
case TOKEN_EXCHANGE:
return handleTokenExchange(response);
case JWT_BEARER:
return handleJwtBearer(response);
default:
throw getApiCaller().unknownAction("/api/auth/token", action);
}
TokenExchanger.java は、Java で書かれた認可サーバーのオープンソースサンプル実装 java-oauth-server におけるトークン交換リクエスト処理のサンプル実装です。
この実装はあくまで例であり、本番環境での利用を想定していません。
以上で、RFC 8693 OAuth 2.0 トークン交換の概要と、Authlete における実装方法の説明を終えます。