RFC 8693 OAuth 2.0 トークン交換

はじめに

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 におけるサポート

このセクションでは、Authlete におけるトークン交換のサポートについて説明します。

RFC 8693 は Authlete 2.3 以降でサポートされています。

トークン交換リクエスト

/auth/token API レスポンスの "action"

クライアントからのトークンリクエストを受信すると、認可サーバーはリクエスト内容をリクエストパラメータの一つとして Authlete の /auth/token API に送信します。この API は、以下の条件を満たす場合に、レスポンスの action パラメータの値として TOKEN_EXCHANGE を返します。

  • トークンリクエストの grant_type リクエストパラメータの値が urn:ietf:params:oauth:grant-type:token-exchange である
  • リクエストが Authlete サーバー側で実施される基本的な検証ステップを通過する

以下の表は、/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 の値に対して、それぞれのトークンタイプに応じた追加の検証を行います。

トークンタイプ: JWT

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 リフレッシュトークンが、現在のサービスに属していることを確認する。

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

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 署名を検証する。

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

No. 検証内容
1 (Authlete はこのトークンタイプに対する検証を行いません。)

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

No. 検証内容
1 (Authlete はこのトークンタイプに対する検証を行いません。)

トークン交換レスポンス

/auth/token/create API{#auth-token-create-api}

認可サーバーは /auth/token API のレスポンスに基づいて必要な検証を実施し、新たに発行するトークンの種類を決定し、クライアントにトークン交換レスポンスを返します。

認可サーバーは、Authlete の /auth/token/create API を使用してトークン交換レスポンスを作成します。この API に grantType パラメータの値として TOKEN_EXCHANGE を指定します。

トークン交換の設定

Authlete でトークン交換を設定するには、以下の手順を実行します。

サービス設定

  1. Authlete 管理コンソール にログインします。

  2. サービス設定に移動します。

  3. エンドポイント > 基本設定 > サポート可能なグラントタイプに移動し、TOKEN_EXCHANGE グラントを有効にします。

  4. トークン&クレーム > 詳細設定 > トークン交換に移動します。

  5. 必要なフラグを有効にして、トークン交換のセキュリティを強化します。

  6. 変更を保存をクリックして設定を適用します。

安全なトークン交換を確保するために、RFC 8693 の範囲外で追加の設定を定義する必要があります。Authlete はこの目的のために以下のオプションを提供しています。

設定オプション タイプ 説明
識別可能なクライアントのみ boolean 識別できないクライアントからのトークン交換リクエストを拒否する。
機密クライアントのみ boolean パブリッククライアントからのトークン交換リクエストを拒否する。
許可されたクライアントのみ boolean 明示的な許可を持たないクライアントからのトークン交換リクエストを拒否する。
暗号化 JWT の拒否 boolean 入力トークンとして暗号化 JWT を使用するトークン交換リクエストを拒否する。
署名なし JWT の拒否 boolean 入力トークンとして署名なし JWT を使用するトークン交換リクエストを拒否する。

クライアント設定

  1. 設定したいクライアントの クライアント設定 に移動します。
  2. トークンとクレーム > 詳細設定 > トークン交換 に移動します。
  3. トークン交換の明示的な許可 を有効にします。これにより、このクライアントがトークン交換リクエストを実行する明示的な許可を得ることができます。この設定は、トークン交換を許可されたクライアントのみに制限する場合に特に重要です。
  4. 変更を保存 をクリックして設定を適用します。

実装例

認可サーバーの実装

`TOKEN_EXCHANGE` アクションの処理{#processing-the-token-exchange-action}

認可サーバーの実装では、TOKEN_EXCHANGE アクションを処理する必要があります。以下の switch 文は、authlete-java-jaxrs ライブラリの TokenRequestHandler.java から抜粋したものです。この switch 文には TOKEN_EXCHANGEcase エントリがあります。

// アクションに応じて分岐
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 における実装方法の説明を終えます。