このエントリは2026/03/27現在の情報に基づいています。将来の機能追加や変更に伴い、記載事項からの乖離が発生する可能性があります。
以前以下のようなエントリを書いたが、それを読んだ人から問い合わせをもらったのが、このエントリを書くきっかけ。
主の質問は以下の通り。
入口のJWT発行元と、OBOで取得したいJWTは別の発行元、しかも異なるIdPサービスもしくはソフトウェアという条件がある場合、Azure API Management (APIM) で、OIDC/OAuth 2.0のOBO (On-Behalf-Of flow) を実現することはできるか?できるとしたらどのような方法が考えられるか?
Microsoft Entraなど、1個のIdPだけで済むならともかく、SaaSや3rd party APIを使う場合には起こりえるので、調査しておくに超したことはなさそう。ただし、前回のエントリに記載した通り、APIMではOBO Flowをネイティブにはサポートしておらず、ポリシーを組み合わせて実現する必要があるので、注意が必要。今回の調査対象のIdPは以下とする。それぞれを組み合わせると12通り。
- Microsoft Entra
- Okta
- KeyCloak
- Auth0
結論から言うと、なかなか渋い結果で、こういうもんかね、という感想。
- これらの組み合わせではCross IdP OBO flowは難しい。
- 条件付きで実現可能な組み合わせはあるが、現時点では Auth0 CTE が Early Access、Keycloak Legacy V1 が Preview であり、いずれも GA の安定機能ではない。そのため、本番採用には機能変更リスク・サポート条件・運用負荷を個別に評価する必要がある。
大前提
APIM は OBO / token exchange の“実行場所”にはなり得るが、APIM 自身は Security Token Service (STS) ではない。APIM が標準で提供するのは以下。
- 入口トークンの検証
- 外部IdPへのHTTP呼び出し
- 接続済みOAuth資格情報の取得
- Managed Identity による Microsoft Entra トークン取得
APIMは、OIDC準拠のあらゆるIdPからのJWTを検証し、任意のトークンエンドポイントに対してHTTP POSTリクエストを発行できるため、実現可否は「APIM にできるか」ではなく、「OBO / token exchange の相手IdPが、その入口JWTを subject_token / assertion として受け付けるか」で決まる。
各IdPのOBOトークン交換におけるトークンの制限は以下の通り。
| IdPのOBOトークン交換 | 制限・前提条件 |
|---|---|
| Microsoft EntraのOBO | Microsoft Entra の OBO 一次情報では、assertion は middle-tier API に送られたアクセス トークンであり、その aud は OBO 要求を行うアプリでなければならないとされている。このため、Okta / Auth0 / Keycloak が発行したトークンを直接持ち込むクロス issuer OBO については、少なくとも一次情報ベースの supported path には確認できない。 Microsoft identity platform and OAuth2.0 On-Behalf-Of flow – Microsoft identity platform | Microsoft Learn |
| Okta のtoken exchange | Okta の token exchange 一次情報は、1 つの custom authorization server または同一 Okta tenant 内の trusted servers を前提としている。 したがって、Entra / Auth0 / Keycloak 発行トークンを subject token とする直接クロス issuer 交換は、少なくとも公式ドキュメント上のサポート範囲外とみなすのが安全。 Set up OAuth 2.0 On-Behalf-Of Token Exchange | Okta Developer |
| Keycloak の標準 token exchange (V2) | 同一 realm 内の Keycloak 発行トークンだけが対象 |
| Keycloak のLegacy Token Exchange V1(preview) | IdP 登録とアカウント リンクがあれば、外部 IdP トークンを Keycloak トークンへ交換可能 |
| Auth0のCustom Token Exchange (Early Access) | Custom Token Exchange(Early Access) は、subject token を検証して Auth0 ユーザーにマッピングするユーザー実装の Action を通じて、任意のトークン形式 を受け入れる |
APIMのCredential Managerが一般的な cross-IdP per-request token exchange の代替ではない理由
APIM Credential Manager は、接続済み OAuth 2.0 backend / SaaS API への delegated access を簡素化する機能であり、一般的な cross-IdP per-request token exchange の代替ではない。とくに get-authorization-context の identity-type=jwt は Microsoft Entra JWT を前提としているため、本件のような非 Entra 入口 token の汎用処理には向かない。
| 理由 | 詳細 |
|---|---|
| 静的かつ事前構成された接続であり、リクエストごとの交換ではない | Credential Manager は、あらかじめ確立された接続に基づいて動作する。 attended(ユーザー委任) シナリオでは、 ❶構成済みユーザーは一度だけサインインして同意すればよく、その後 API Management インスタンスが接続を作成し管理する ❷実行時には、get-authorization-context ポリシーが保存済みトークンを取得し、期限切れであれば更新する これは、各受信リクエストの JWT を都度ターゲット IdP 用の新しいトークンに交換する OBO とは根本的に異なる。Credential Manager は、そのようなリクエスト単位の交換を行わない。 |
| Unattended モードでは、リクエストごとのユーザー コンテキストがない | unattended シナリオでは、API 呼び出しはユーザー コンテキストに関連しない事前構成済み接続を利用するため、すべてのユーザーに同じデータが返される。 つまり、1 つの保存済みトークンがすべての呼び出し元に使い回されてしまう。OBO は定義上、呼び出し元ユーザーのアイデンティティ をダウンストリーム トークンに伝播させる必要があるが、このモードではそれが実現できない。 |
| Attended モードは Microsoft Entra の ID に限定される | Attended シナリオでは、Microsoft Entra ユーザーまたはグループ ID を代理して接続へのアクセスを有効化するのであって、Okta、Auth0、Keycloak で認証されたユーザーは、この委任メカニズムに直接参加できない。そのため、「入口トークンが非 Entra IdP から来る」という本件ユースケースの前提条件を満たせない。 |
Credential Manager は、SaaS バックエンド接続や Logic Apps 的な統合を含む、バックエンド OAuth 2.0 API のトークン ライフサイクル管理を簡素化するが、OBO を定義づける リアルタイムかつアイデンティティ保持型のトークン交換 は実行しない。Credential Manager を Cross-IdP 委任に使うと、(unattended では)呼び出し元のアイデンティティが失われるか、(attended では)呼び出し元が最初から Microsoft Entra の ID を持っている必要がある。
IdP ごとのトークン交換機能について
a) Microsoft Entra
Microsoft identity platform の OBO フローは、ユーザーの ID と権限をリクエスト チェーン全体に渡すために、/oauth2/v2.0/token エンドポイントでアクセス トークンを交換する。主なパラメータと制約は以下の通り。
| パラメータ | 値 / 要件 |
|---|---|
grant_type | urn:ietf:params:oauth:grant-type:jwt-bearer |
assertion | OBO 要求を行うアプリの client_id と一致する aud クレームを持つアクセス トークンであること。「アプリは別のアプリ向けトークンを redeem できない(たとえば、クライアントが API に Microsoft Graph 向けトークンを渡した場合、その API は OBO でそれを redeem できない)」 |
requested_token_use | on_behalf_of |
| ユーザー主体のみ | 「OBO フローはユーザー主体に対してのみ動作する」。サービス プリンシパルの app-only トークンは OBO で交換できず、代わりに Client Credentials を使う必要がある。 |
| カスタム署名キー | 「カスタム署名キーを持つアプリケーションは OBO フローの中間 API として使えない」 |
Cross-IdP シナリオにおける決定的制約
assertion は、Entra 発行の ユーザー アクセス トークンであり、その aud が中間層アプリの Azure AD client_id と一致している必要がある。Okta、Auth0、Keycloak からの JWT は、それぞれ外部 issuer(iss)、外部 audience 形式、外部署名キーを持つため、Entra のトークン エンドポイントでは検証できず、OBO 要求は拒否される。jwt-bearer グラントは Microsoft 独自拡張であり、RFC 8693 準拠ではない。
b) Okta
Okta は RFC 8693 の token exchange(grant_type=urn:ietf:params:oauth:grant-type:token-exchange)を実装しているが、その適用範囲は明確に制限されていて、「On-Behalf-Of Token Exchange は 1 つの custom authorization server で利用できる。また、同一 Okta テナント内の他の custom authorization servers 間でも利用できる」とされている。
構成要件:
- サービス アプリで Token Exchange を有効化する必要がある(Grant type セクションの Advanced から Token Exchange を選択)
- Authorization server 側で custom scopes を作成する必要がある。「サービス アプリ起点の token exchange では
offline_accessや OpenID Connect scopes は含められない。つまり refresh token や ID token は要求できない」 - 同一テナント内の複数 authorization server 間で交換する場合は Trusted servers を構成する必要がある
外部トークン拒否は明示されている。Okta の Knowledge Base では、token exchange 時の "subject token is invalid" エラーの原因として、以下が挙げられており、推奨される確認事項として「issuer claim にある authorization server が、token call で使用している authorization server と同じであることを確認する」との記載がある。
-
expの期限切れ -
aud不一致 — 「subject_token のaudが token exchange を行うアプリのclient_idではない」 - issuer 不一致 — 「トークンの
issが、token endpoint で使用している authorization server と異なる」
そのため、同一 Okta テナント内の authorization server 以外から発行されたトークンは、(3) により拒否され、Cross-IdP 交換は不可能。
c) Auth0
Auth0 は Custom Token Exchange を提供している(Enterprise および B2B Pro 向け Early Access)。この機能は「RFC 8693 に従い、アプリケーションが既存トークンを Auth0 トークンへ交換できるようにする」もので、想定ユースケースとしては、「別 audience 向け Auth0 トークンの取得」「外部 identity provider との統合」「Auth0 への移行」が挙げられている。
仕組みは完全にプログラマブル。
- 各 token exchange 要求は、Custom Token Exchange Profile にマップされ、その Profile は 1 つの Action(Auth0 のサーバーレス関数)によって制御
- Action の中で開発者は、
subject_tokenを decode / validate し、さらに 認可判断を行い、ユーザーを設定する コードを書く - 「Custom Token Exchange で使用する subject token は、Action コードが解釈できる限り、任意のトークン形式・型でよい。受け取って受け入れるトークンについて、強固な検証を実装しなければならない」と明記されている
現在の制約(Early Access):
- MFA API メソッド(
challengeWith()/EnrollWith())は未対応 - import mode
ONの Custom DB Connections はsetUserByConnection()では未対応 actor_tokenやactorclaim などの委任情報は未対応- サードパーティおよび non-OIDC conformant クライアントは未対応
- 非対話型フローでは同意を取得できないため、ターゲット API で Allow Skipping User Consent の有効化が必要
Auth0 ドキュメントの “Re-use an external authentication provider” 例は、Cross-IdP OBO に直接対応するパターン。パートナー アプリのユーザーが外部 IdP で認証され、その外部 ID token を subject_token として Auth0 の /oauth/token に grant_type=urn:ietf:params:oauth:grant-type:token-exchange で渡し、Auth0 access token に交換する。Action は外部トークンを検証し(たとえば外部 IdP の JWKS を利用)、api.authentication.setUserByConnection() を通じて外部 ID を Auth0 ユーザーへマップする。
d) Keycloak
Keycloak はトークン交換を 2 系統で実装している。
| 機能 | Standard Token Exchange V2 | Legacy Token Exchange V1 |
|---|---|---|
| 既定状態 | 既定で有効 | 既定で無効(preview) |
| 対応ユースケース | (1) Internal のみ: 同一 realm 内 Keycloak token → Keycloak token | 4 つすべて: (1) internal (2) KC→external (3) external→KC (4) impersonation |
| 互換性 | 正式サポート、今後も維持 | 将来の Keycloak バージョンで後方互換が保証されない可能性がある |
| 依存機能 | クライアント スイッチ有効化のみ | Fine-grained admin permissions version 1 (FGAP:v1) が必要 |
Cross-IdP OBO で関係するのは (3) external token → Keycloak token。要求を行うクライアントは、適切な token exchange 権限を持つ confidential client である必要がある。
external-to-internal 交換では subject_issuer パラメータが必要。これは「その realm に設定された Identity Provider の alias、または特定の Identity Provider で設定された issuer claim identifier」でなければならない。subject_token_type は urn:ietf:params:oauth:token-type:access_token または urn:ietf:params:oauth:token-type:jwt のいずれかでなければならない。
Keycloak のドキュメントにある external-to-internal 交換の curl 例では、以下のように指定している。
-d "subject_issuer=myOidcProvider" \
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token"
外部 IdP は Keycloak に OIDC Identity Provider として登録されている必要があり、交換対象ユーザーは Keycloak 側とアカウント リンク済みでなければならず、リンクがなければ交換は失敗する(SAML の identity provider は token exchange ではサポートされない)。
さらに FGAP:v2 は「token exchange permissions をサポートしない。これは token-exchange が“管理権限”ではないという考えに基づき、FGAP:v2 に追加する予定はない」とされている。したがって Legacy V1 の利用には、古い FGAP:v1 を有効化する必要がある。
Cross-IdP OBO 実現可否マトリクス
ターゲットが Microsoft Entra または Okta の組み合わせはすべて不可能。ターゲットが Auth0 または Keycloak の組み合わせは、条件付きで可能。
| # | Entry IdP | OBO Target IdP | Feasible? | 制約 / 有効化要件 |
|---|---|---|---|---|
| 1 | Microsoft Entra | Okta | No | Okta は非 Okta トークンを拒否(issuer mismatch) |
| 2 | Okta | Microsoft Entra | No | Entra は自分自身のトークンを assertion として要求 |
| 3 | Microsoft Entra | Keycloak | Depends | Keycloak Legacy V1(preview)+ IdP 登録 + (既存ユーザーに解決される場合は)account linking |
| 4 | Keycloak | Microsoft Entra | No | Entra は自分自身のトークンを assertion として要求 |
| 5 | Okta | Keycloak | Depends | Keycloak Legacy V1(preview)+ IdP 登録 + (既存ユーザーに解決される場合は)account linking |
| 6 | Keycloak | Okta | No | Okta は非 Okta トークンを拒否(issuer mismatch) |
| 7 | Microsoft Entra | Auth0 | Depends | Auth0 Custom Token Exchange(Early Access)+ Action コード |
| 8 | Auth0 | Microsoft Entra | No | Entra は自分自身のトークンを assertion として要求 |
| 9 | Okta | Auth0 | Depends | Auth0 Custom Token Exchange(Early Access)+ Action コード |
| 10 | Auth0 | Okta | No | Okta は非 Okta トークンを拒否(issuer mismatch) |
| 11 | Keycloak | Auth0 | Depends | Auth0 Custom Token Exchange(Early Access)+ Action コード |
| 12 | Auth0 | Keycloak | Depends | Keycloak Legacy V1(preview)+ IdP 登録 + (既存ユーザーに解決される場合は)account linking |
ターゲット IdP ごとの詳細分析
a) ターゲット: Microsoft Entra ID(#2, #4, #8)— No
Entra の OBO エンドポイントは、assertion について、中間層 API に送られたアクセス トークンであり、かつその aud はこの OBO 要求を行うアプリ(client-id で表されるアプリ)でなければならない。grant_type は urn:ietf:params:oauth:grant-type:jwt-bearerであり、これは Microsoft 独自拡張で、RFC 8693 準拠ではない。外部トークンを示す subject_issuer のようなパラメータは存在しない。
| IdP | issuer |
|---|---|
| Okta | https://{org}.okta.com/oauth2/{serverId} |
| Auth0 | https://{tenant}.auth0.com/ |
| Keycloak | https://{host}/realms/{realm} |
Microsoft Entra については、OBO 一次情報が assertion を middle-tier 宛て access token と定義しており、外部 issuer token を直接持ち込む supported path は確認できない。したがって、Okta / Auth0 / Keycloak token を入力にする direct cross-issuer OBO は、少なくとも公式ドキュメント上は非サポート扱いが妥当である。
対話的フェデレーションで代替できそうに見えるが、そうはいかない。
確かにOkta を Entra で SAML 2.0 または OIDC IdP として構成できるが、この構成はブラウザベースの SSO であり、API レベル OBO token exchange ではない。Auth0 や Keycloak についても同様で、SAML/OIDC federation はインタラクティブなログイン パスを作るだけで、プログラマブルな token exchange grant にはならない。
b) ターゲット: Okta(#1, #6, #10)— No
Okta については、token exchange の一次情報が単一 custom authorization server または同一 tenant 内 trusted servers を前提としている。したがって、外部 issuer token を直接 subject token とする交換は、少なくとも公式ドキュメント上のサポート範囲外とみなすのが安全である。
外部 JWT が subject_token として渡されると、Okta はそのトークンに対して、以下の3点を検証する。
(1) 期限切れ
(2) 呼び出し元アプリの client_id に対する audience 一致
(3) token endpoint を処理している authorization server に対する issuer 一致
Entra、Keycloak、Auth0 からのトークンは issuer チェックで失敗し、
{
"error": "invalid_grant",
"error_description": "subject token is invalid"
}
を返す。
外部 IdP トークンを登録できる Trusted servers のような仕組みはない。Trusted servers はあくまでテナント内複数 authorization server 間のユースケース専用。
c) ターゲット: Keycloak(#3, #5, #12)— Depends
これらの組み合わせは、Keycloak の Legacy Token Exchange V1 により実現可能。これはユースケース(3)「外部トークンを Keycloak トークンに交換する」をサポートするためであるが、 preview であり、既定では無効である点に注意が必要。
Keycloak 側構成(3 パターン共通):
| 1 | Legacy Token Exchange を有効化 | Legacy Token Exchange V1 は preview で既定無効であり、Keycloak docs に従って preview / token-exchange 機能を有効化する必要がある。 あわせて、V1 を使うには FGAP:v1 が必要。具体的な起動フラグは Keycloak バージョン差分の影響を受けうるため、利用バージョンの公式 docs を参照して決めること。 Configuring and using token exchange – Keycloak |
| 2 | 外部 IdP を OIDC Identity Provider として target realm に登録 | 【Entra ID が entry の場合】 Entra の OIDC discovery URL( https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration)、Entra app registration から取得した client ID / secret を設定し、alias(例: azure-ad)を割り当てる【Okta が entry の場合】 Okta の OIDC discovery URL、client credentials、alias(例: okta)を設定【Auth0 が entry の場合】 Auth0 の OIDC discovery URL( https://{tenant}.auth0.com/.well-known/openid-configuration)、client credentials、alias(例: auth0)を設定Configuring and using token exchange – Keycloak |
| 3 | 外部ユーザーと Keycloak ユーザーの account linking を確立 | Keycloak は、外部 IdP 登録を前提に external-to-internal exchange を行えるが、account linking は常時必須ではなく、既存ユーザーに解決される場合の要件である。 一方、既存ユーザーにマップされない場合は、新規ユーザーの import が行われる可能性がある。 |
| 4 | confidential client を構成 | APIM または broker service を表す confidential client を Keycloak に作成し、token exchange の権限を与える。 |
APIM ポリシー実装:
❶<validate-jwt> — entry IdP の OIDC metadata に対して受信トークンを検証し、iss、aud、署名を確認。
❷<send-request> — Keycloak の token endpoint に POST する。
POST /realms/{realm}/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
client_id={keycloak_confidential_client_id}&
client_secret={keycloak_client_secret}&
subject_token={incoming_jwt_from_entry_idp}&
subject_issuer={alias_of_external_idp_in_keycloak}&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
requested_token_type=urn:ietf:params:oauth:token-type:access_token
subject_issuer は、登録済み外部 IdP の alias と一致している必要がある。標準の交換リクエスト形式は Keycloak ドキュメントに示されており、外部トークンの場合はこれに subject_issuer が加わる。
❸レスポンス トークンを取り出す
Keycloak は access_token、expires_in(例: 300)、token_type(Bearer)、issued_token_type、scope を含む JSON を返す。<set-variable> と <set-header> を使って、この新しいトークンをバックエンド リクエストに付与する。
send-request + トークン抽出の APIM ポリシー パターンは、Azure API Management and OAuth tokens for multiple backend services – Ludovic Médard というエントリでカバーしている、Azure AD 同一 IdP OBO の例で実証されている。そこでは、受信 Authorization ヘッダーから bearer token を取り出し、<send-request> で token endpoint に POST し、JSON レスポンスから access_token を取り出して、バックエンド転送前に Authorization ヘッダーを上書きしている。Cross-IdP の Keycloak ケースでも、token endpoint と grant parameters(token-exchange、subject_token、subject_issuer)が異なるだけで、パターン自体は同じ。
トレードオフ:
| preview 安定性リスク | Legacy V1 は将来の Keycloak バージョンで後方互換が保証されない可能性がある。 本番導入時はアップグレードで破壊的変更が起こりうることを前提にする必要がある。 |
| account linking の運用負荷 | 最初の交換が成功する前に、各ユーザーが Keycloak とリンク済みである必要があるため、初回ログインまたは一括プロビジョニングのようなオンボーディング ステップが必要。 |
| SAML IdP は対象外 | token exchange で使える外部 IdP は OIDC のみ。SAML ベース構成は不可。 |
| FGAP:v1 依存 | FGAP:v1 を有効化することで管理面の複雑性が増す。 Keycloak チームは FGAP:v2 へ token exchange permissions を追加する予定はないと明言しており、これは一時的ではなく構造的制約として捉えるべき。 |
ターゲット: Auth0(#7, #9, #11)— Depends
これらの組み合わせは、Auth0 の Custom Token Exchange(Early Access)で実現可能です。Auth0 は、テナントで機能が有効化され、Custom Token Exchange Profile と Action が構成されていれば、任意の外部トークンを subject_token として受け入れます【1†L10-L25】。
Auth0 側構成(3 パターン共通)
| 1 | Custom Token Exchange を有効化 | 対象 Auth0 テナントでこの Early Access 機能が有効になっているか確認(Enterprise および B2B Pro 向け機能) |
| 2 | Custom Token Exchange Profile を作成 | 交換後トークンを受け取るアプリケーションに対して Profile を作成。 各 Profile は 1 つの Action と関連付けられる。 |
| 3 | Action(Node.js)を実装 | 外部 IdP からの subject_token を検証外部 IdP の JWKS エンドポイントを取得し、JWT の署名、 iss、aud、exp、その他必要な claim を検証。Auth0 は jose ライブラリを利用した JWKS ベース検証のコード例を提供認可ポリシーを適用 交換を許可するかどうかを判定(例: scope、roles、domain 制約など)。拒否する場合は api.access.deny() を利用。Auth0 側のユーザーを設定 外部ユーザーを Auth0 の特定接続内ユーザーへマッピングするには api.authentication.setUserByConnection()、既存 Auth0 ユーザー ID を使う場合は api.authentication.setUserById() を利用ユーザーが存在しない場合は creationBehavior: 'create_if_not_exists' を利用可 |
| 4 | ターゲット API を構成 | 非対話型フローでは consent を取得できないため、Auth0 の API リソース側で Allow Skipping User Consent を有効にしておく必要がある |
APIM ポリシー実装:
❶<validate-jwt> — entry IdP の OIDC metadata に対して受信トークンを検証
❷<send-request> — Auth0 の token endpoint に POST する
POST https://{auth0_domain}/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token={incoming_jwt_from_entry_idp}&
subject_token_type={custom_or_standard_type}&
audience={auth0_api_identifier}&
scope={desired_scopes}&
client_id={auth0_client_id}&
client_secret={auth0_client_secret}
subject_token_type は標準 URN でも独自識別子でもよく、Profile 定義と一致していればよい。Auth0 は要求を対応する Custom Token Exchange Profile にマップし、Action を実行する。
❸レスポンスを取り出す
成功時、Auth0 は、マッピングされたユーザーに対する Auth0 発行の access token / ID token / refresh token を返すので、これを取り出してバックエンド リクエストへ付与する。
トレードオフ
| Early Access ステータス | まだ GA ではないため、GA 前に挙動が変わる可能性がある。 |
| セキュリティ責任が開発者にある | 受け取って受け入れるトークンについて、強固な検証を実装しなければならない。そうしないと spoofing や replay attack などの攻撃に対して脆弱になる。 Auth0 では検証責任のすべてを Action 開発者が負う。 |
actor_token / delegation chain 非対応 | Early Access 版では「Specific delegation support(例: actor_token や actor claim)」が未対応。そのため、「元のユーザー + 代理動作するサービス」という完全な OBO 委任チェーンを Auth0 トークン上で表現できない。 |
| 一方向のみ | Auth0 Custom Token Exchange は Auth0 トークンを生成するだけであり、Auth0 トークンを外部 IdP トークンへ交換することはできない。 |
| Enterprise/B2B Pro 必須 | 下位プランでは利用不可 |
IdP 間プロトコル比較
| 属性 | Microsoft Entra | Okta | Auth0 (Custom Token Exchange) | Keycloak (Legacy V1) |
|---|---|---|---|---|
| Grant type | jwt-bearer | token-exchange | token-exchange | token-exchange |
| 入力トークン パラメータ | assertion | subject_token | subject_token | subject_token |
| 外部 IdP トークン受入 | No | No (同一テナント内のみ) | Yes (任意形式、Action による) | Yes ( subject_issuer により) |
| 外部トークンの検証 | N/A | N/A | Action コードで開発者が実装 | 検証処理が Keycloak 側 IdP 設定に寄る |
| ユーザー マッピング | N/A | N/A | 開発者実装(setUser API) | account linking による自動 |
| RFC 8693 準拠 | No (独自 jwt-bearer) | Yes(ただし同一テナント内に限定) | Yes | 部分的 / loose implementation(preview, delegation semantics は未サポート) |
| 機能成熟度 | GA | GA | Early Access | Preview |
ポイント
- Okta と Keycloak はともに RFC 8693 の
token-exchangegrant を使い、Auth0 も RFC 8693 に従うが、Okta は適用範囲を同一テナント内に制限しているため、Cross-IdP では使えない。 - Entra は独自の
jwt-bearergrant を使うため、RFC 8693 ベースの exchange と根本的に非互換。 - Auth0 は最大の柔軟性(任意トークン形式、任意検証ロジック)を提供するが、その分セキュリティ責任を全面的に開発者へ委ねる。
- Keycloak は、外部 IdP 登録を前提に external-to-internal exchange を行えるが、account linking は常時必須ではなく、既存ユーザーに解決される場合の要件。
- 条件付きで可能な 2 つのターゲット IdP(Auth0 と Keycloak)のいずれも、現時点で GA の完全サポート機能ではない。
APIM が果たす「プロトコル媒介層」としての役割
このアーキテクチャにおいて、APIM は次の 2 つのポリシーを提供する。
| Policy | |
|---|---|
<validate-jwt> | OIDC metadata URL、期待する iss、aud を指定することで、任意の OIDC 準拠 IdP の JWT を検証する。これは Entra、Okta、Auth0、Keycloak のトークンに対して同じように機能する。 |
<send-request> | 任意の token endpoint に対してカスタム form パラメータを持つ HTTP POST を送信し、受信トークンを使って token exchange リクエストを構築できる。 |
このパターンは、同一 IdP(Entra → Entra)の OBO シナリオですでに実証済み。APIM は受信 Authorization ヘッダーから Bearer token を変数へ取り出し、それを assertion として https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token に grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer などのパラメータ付きで send-request し、JSON レスポンスから access_token を取り出してバックエンドへ送る前に Authorization ヘッダーを上書きする。
OBOトークンを逐一取得するのは高コストなので、キャッシュに一定期間保持して毎回 token exchange を行うことによる約 250ms のオーバーヘッドを避けることも推奨事項。具体的には以下のあたり。
- ユーザーごとOBO トークンを 3300 秒までキャッシュ
unique_nameとclientIdの組み合わせでユニークにする
Azure API Management and OAuth tokens for multiple backend services – Ludovic Médard
このsend-request パターンは Cross-IdP シナリオでもそのまま使える。置き換えるのは、ターゲット IdP の token endpoint URL、適切な grant_type(Keycloak/Auth0 なら token-exchange)、および必要パラメータ(subject_token、subject_issuer など)だけ。APIM の能力自体は制約ではなく、制約はターゲット IdP がその外部トークンを受け入れるかどうかである。
このような説明をしたところ、更問をもらったのだが、長くなったので次のエントリに記載する。
Resources
- Microsoft identity platform and OAuth2.0 On-Behalf-Of flow – Microsoft identity platform | Microsoft Learn
- Set up OAuth 2.0 On-Behalf-Of Token Exchange | Okta Developer
- Error “subject token is invalid” During OAuth 2.0 Token Exchange Flow
- Custom Token Exchange – Auth0 Docs
- Example Use Cases – Auth0 Docs
- Configuring and using token exchange – Keycloak
- About Credential Manager in Azure API Management | Microsoft Learn
- Integrate Microsoft Entra ID as an Identity Provider for Okta and Vice Versa
- RFC 8693 – OAuth 2.0 Token Exchange
- Azure API Management and OAuth tokens for multiple backend services – Ludovic Médard