このエントリは2026/03/27現在の情報に基づいています。将来の機能追加や変更に伴い、記載事項からの乖離が発生する可能性があります。
このエントリは前回の続き。
前回の説明をしたところ、以下のような更問をもらった。
なるほど、状況は理解した。
では、Azure API Management (APIM) を中心にしつつ、他のコンポーネントを組み合わせてCross IdP OBO flowを実現することはできるか?できるとしたら、どのような方法が考えられるか?
各IdP固有のロジックを切り出して、Service calloutの形でAPIMから利用できればよさそうに見える。
Executive Summary
APIM の役割は入口 JWT の検証、ルーティング、broker 呼び出しであり、実際の token 変換ロジックは broker 側で持つのが自然であるため、APIM 自体を STS 化することではなく、APIM の後段に別アプリケーションを置いて、各 IdP ごとの token exchange / OBO / JWT grant を実装するのがよい 。APIM には validate-jwt、send-request、authentication-managed-identity といった部品はあるが、foreign token を任意 IdP の token に変換する汎用 STS 機能は公開されていない。
したがって実装の中核は、Azure Functions または Azure Container Apps 上に broker API を作り、その内部に target IdP 別の adapter を置くようなしくみ。Entra adapter は Entra OBO の前提を満たす場合だけ、Okta adapter は same-tenant custom authorization server world のときだけ、Auth0 adapter は Custom Token Exchange Action/Profile を前提に、Keycloak adapter は V2 / JWT Authorization Grant / chaining のどの経路を使うかを分けて実装する。つまり、各 IdP ごとの交換ロジックを実装する、ということ。
実装基盤としては、軽量な HTTP オーケストレータなら Azure Functions、複数 adapter・custom library・internal ingress・sidecar・柔軟なスケーリングが必要なら Azure Container Apps が適している。さらに Functions on Container Apps を選べば、Functions のプログラミングモデルを維持しつつ、Container Apps のネットワーク・KEDA・revision 管理を使えるが、そのあたりはお好みで。
custom broker / 独自 STS が実際に担う責務
custom broker と 独自 STS には実務上 2 つの意味がある点に注意が必要。
- translation broker
- target IdP 自身の
/tokenendpoint を呼び、最終的には target IdP 発行の token を得る方式。 - Entra / Okta / Auth0 / Keycloak 保護 API に振り分けるのであれば、この方式でよい。
- RFC 8693 が想定する「resource server が client となり downstream 向け token を得る」考え方にも合致する。
- target IdP 自身の
- internal STS
- broker 自身が JWT を発行し、downstream がその broker を信頼する方式。
- これは自社管理の API 群に共通トークンを配る設計としては成立するが、Entra / Okta / Auth0 / Keycloak 保護 API に対して vendor-native token を得る話とは別なので注意が必要。
この問い合わせ文脈で求められているのは、基本的に 1. translation broker、つまり、broker が target IdP ごとの交換条件を満たす request を組み立て、target IdP から token を受け取る 実装である。
アーキテクチャ案
前エントリに記載した6つの実現不可能な組み合わせおよび、6つの条件付き実現ケースを本番向けに堅牢化する場合には、Azure Functions(もしくはそれに類するもの。Container Appsでもよい) で実装するカスタム broker service を用意するのが最も現実的である。これには以下の目的・利得がある。
- IdP 固有の交換ロジックを APIM から切り離す
- メンテナンスをアプリケーション コード側(ここではAPIMではなく、FunctionsやContainer Apps)に集中する

| Step | 処理 | 詳細 |
|---|---|---|
| 1, 2 | Client → APIM(IdP A の JWT) | クライアントは IdP A(例: Okta)で通常の OAuth 2.0/OIDC フローを通じて認証され、そのアクセス トークンを Authorization ヘッダーに入れて APIM 経由で API を呼び出す |
| 3 | APIM が受信 JWT を検証 | APIM の <validate-jwt> ポリシーが IdP A の OIDC metadata(JWKS、issuer、audience)に対してトークンを検証する。失敗した場合は HTTP 401 を返し、成功した場合は、ユーザー ID、メール、roles などの claim をコンテキスト変数へ抽出する。 |
| 4 | APIM が Custom Broker APIに転送 | <send-request> を使って、APIM は検証済み IdP A トークン(または抽出済み claim)を Azure Function に送る。APIM と Custom Broker API間の通信は managed identity、function key、mTLS などで保護する。 |
| 5, 6 | Custom Broker APIのIdP Specific Adapterが Cross-IdP token acquisition を実行 | 各IdPに固有のロジックを実行するAdapterが、ユーザーを代理した新しい IdP B トークンを取得して APIM に返す。 |
| 7 | APIM が IdP B トークンでバックエンドに転送 | APIM は IdP B トークンを Authorization ヘッダーへ設定し、元の API リクエストをバックエンド サービスへ転送する。バックエンドは IdP B トークンを検証し、処理を行う。 |
この構成での責務分離は次のとおり。
| コンポーネント | 主な責務 |
|---|---|
| APIM | 入口 JWT の検証、issuer/audience/required-claims による早期 reject、target route の選択、broker 呼び出し、最終 backend call |
| Broker | source token の provider-specific 前提チェック、user / subject mapping、target IdP 向け token request の生成、例外処理 |
| Key Vault | client secret、certificate、partner 連携用 secret、固定公開鍵や補助設定の保管 |
| Target IdP | 実際の token issuance |
重要なのは、provider-specific な分岐を APIM policy に寄せすぎないこと。 APIM policy は分岐や呼び出しはできるが、複数 IdP の subject-token 条件、user mapping、error normalization を大きく書き込むには保守性が悪くなるためである。APIM は edge、broker は identity translation logic、という分離が実装しやすい。
リクエストの基本フロー

このフローで APIM が使う基本部品は明確。
| APIM policy | Usage |
|---|---|
validate-jwt | JWT の存在、署名、issuer、audience、claims を入口で検証する |
send-request | broker や token endpoint への HTTP request を送信する |
authentication-managed-identity | broker 側を Microsoft Entra 保護 API として公開した場合、APIM -> broker hop を managed identity で保護できる。重要なのは、 source / target IdP が Entra でなくても使える点。Entra を使うのは APIM と broker の hop 保護 であって、exchange 先 IdP を Entra に固定する話ではない点に注意。 |
一方、get-authorization-context は managed connection の token 取得には便利だが、incoming foreign JWT を per-request で別 IdP token に変換する generic Cross-IdP OBO の答えではない点に注意が必要。 identity-type="jwt" の identity は Microsoft Entra JWT を前提にしており、期待 aud も https://azure-api.net/authorization-manager であるため。
Broker の内部設計
| Part | Usage |
|---|---|
| Request normalizer | APIM から受けた request を sourceIssuer / targetProvider / targetAudience / requestedScopes / sourceToken に正規化。 |
| Policy / route resolver | 「この issuer からこの target への交換を許可するか」を allowlist で判定。 |
| Subject mapper | 外部 sub を target 側 user / principal と結び付ける。Auth0 Action や Keycloak の linked user がここに相当。 |
| Provider adapter | Entra / Okta / Auth0 / Keycloak ごとに request form を組み立てる。 |
| Secret / key access | client secret、certificate、固定 audience mapping などを Key Vault から取得。 |
以下のような API contract にしておくと、APIM 側の責務を軽く保つことができる。
{
"sourceToken": "<JWT>",
"sourceIssuer": "https://issuer.example",
"targetProvider": "auth0",
"targetTenant": "example-tenant",
"targetAudience": "https://api.example",
"requestedScopes": ["read:data"],
"subjectHints": {
"sub": "248289761001",
"email": "user@example.com"
}
}
当然ながら、broker はこの入力の検証をする必要がある。targetProvider や targetTenant を caller 任せにせず、APIM product / API / operation ごとに許可済み route を固定し、broker 側でも再検証する(Azure の公開ドキュメントそのものではなく、前記の APIM / Key Vault / managed identity 機能を前提にした実装上の推奨)。
Azure Functionsを使うか、Azure Container Apps (ACA) を使うか
Azure Functions の HTTP trigger は broker API を簡単に作るのに向いているが、HTTP trigger の応答時間は最大 230 秒である点に注意が必要。通常の token exchange 自体は短時間で終わるはずなので実務上は十分なことが多いが、JIT user provisioning、外部属性参照、重い policy engine 呼び出しまで 1 request に詰め込むのであれば、ACAが優位。
一方で Azure Container Apps は、API endpoint / microservice / event-driven processing をまとめて扱う前提のサービスで、internal ingress、KEDA による scale、managed identity、Key Vault reference、Log Analytics が揃っていることから、複数 adapter をモジュール化する今回の用途とは相性がよい。
| 実装基盤 | 向くケース | この問い合わせでの評価 |
|---|---|---|
| Azure Functions | 軽量な HTTP broker 少数 adapter、サーバレス優先 | HTTP trigger で broker API を作成でき、managed identity で Key Vault に接続できる。 |
| Azure Container Apps | 複数 adapter、custom library、internal/private 志向、sidecar、柔軟な scale | API endpoint、microservice、KEDA、internal ingress、managed identity、Key Vault secret reference が利用可能。 |
| Functions on Container Apps | Functions のコードモデルを維持しつつ、ACA の機能も欲しい | Functions を containerized function app として動かし、KEDA / VNet / revision / unified environment を利用可能。 |
IdP ごとの adapter 実装
Entra Adapter
Entra の documented OBO は、grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer と requested_token_use=on_behalf_of を使う proprietary flow。しかも assertion は その OBO request を行う middle-tier app を aud に持つ Entra access token でなければならず、OBO は user principal 向け。
したがって broker 側の Entra adapter は、任意の foreign token を Entra token に変換する adapter にはなり得ない。 実装できるのは、たとえば「client は最初から Entra で broker 用 audience の access token を取得しており、broker がその token を使って downstream Entra API 用 token を取る」ケースのみ。Okta / Auth0 / Keycloak 発行 token をそのまま Entra OBO に流す adapter は、documented path にはならない。
実装要点
assertionに入れるのは Entra access token- その
audは broker confidential client か、それに等価な middle-tierアプリ - foreign issuer token は早期 reject
Okta adapter
Okta は RFC 8693 ベースの OBO / token exchange を案内しているが、公開 docs の前提は single custom authorization server または same Okta tenant 内の custom authorization server 同士 であり、trusted servers も same tenant 前提である。
したがって broker 側では、source token がその Okta tenant world に属している時だけ Okta adapter を有効にするよう構成する。つまり、foreign issuer token を generic に受ける adapter として実装するのではなく、same-tenant Okta rule をコード化する adapter。加えて service app initiated flow では offline_access や OpenID Connect scopes を要求できないため、refresh token や ID token を前提にした設計にもできない点に注意が必要。
Auth0 adapter
Auth0 は 4 製品の中で、broker 方式と最も相性がよい target であり、Custom Token Exchange (CTE) は /oauth/token で既存 token を Auth0 token に交換する機能で、Action が subject_token を decode / validate / authorize し、user を set すると Auth0 token が返る。external IdP token の再利用も明示されている。
つまり Auth0 adapter の実装は、broker が target Auth0 tenant の /oauth/token に対して、許可済み subject_token_type で request を作る形になるのだが、Auth0 側では次の前提を満たす必要がある。
- アプリは first-party かつ OIDC-conformant
subject_token_typeと Action は 1 対 1 の Profile で管理urn:ietfなどの予約 namespace は customsubject_token_typeに使えない- target API では Allow Skipping User Consent が必要
- trust と user mapping は Action 実装が担う
このため、Auth0 adapter は単なる HTTP 呼び出しではなく、target tenant に用意した Action/Profile と表裏一体で実装する必要がある。
Keycloak adapter
Keycloakには実装方式が複数存在するが、それぞれ制約がある。
| 方式 | 制約 |
|---|---|
| Standard token exchange V2 | fully supported だが same realm の internal-to-internal に限定される |
| Legacy V1 | external-to-internal を含むが preview / deprecated |
| JWT Authorization Grant | 外部 IdP との trust を張って cross-domain / cross-realm access token を得る (external OIDC / JWT assertion を受ける) |
| Chaining | RFC 8693 と RFC 7523 を組み合わせた方式 (domain A 側 exchange + domain B 側 JWT grant) |
特に JWT Authorization Grant では、Keycloak 側が iss / sub / aud / exp / signature を検証し、external subject と Keycloak user のリンクも必要である。これはbroker 実装というよりはむしろ、target Keycloak 側の trust 設定が成立しているか が重要。
加えて、JWT Authorization Grant を要求できるのは confidential client のみ。したがって broker が Keycloak 側で使う client registration は、public client ではなく client secret または certificate を持つ confidential client として構成する必要がある。
Entra / Okta ターゲット向け fallback 実装の考え方
Entra では、OBO の assertion に middle-tier 宛て Entra access token を要求しており、direct cross-issuer OBO はサポートしていない。Okta では、公式ガイドの token exchange は single custom authorization server または同一 tenant 内 trusted servers に限定されており、外部 issuer token の直接交換に関する記述は確認できないため、サポート対象外と考えるのが妥当。
| IdP | 制限 | 参考 |
|---|---|---|
| Entra | assertion に middle-tier 向け Entra access token を要求すること | Microsoft identity platform and OAuth2.0 On-Behalf-Of flow – Microsoft identity platform | Microsoft Learn https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow |
| Okta | single custom authorization server または同一 tenant 内 trusted servers に限定されており、外部 issuer token の直接交換に関する記述は確認できない。なお support KB では、iss / aud / exp などの不整合で subject token is invalid / invalid_grant になる代表例が示されている。 | Error “subject token is invalid” During OAuth 2.0 Token Exchange Flow https://support.okta.com/help/s/article/error-subject-token-is-invalid-during-oauth-2-0-token-exchange-flow?language=en_US |
そのため Broker 実装では、少なくとも次の 2方法からどちらかを選択する必要がある。ただし、いずれの方式も厳密な意味でのCross-IdP OBOではない点は注意が必要。
- 保存済み refresh token を使う
事前の interactive consent で得た refresh token をユーザーごとに安全に保存し、Broker で refresh grant を行う。 - client credentials fallback
ユーザー コンテキストを捨てて app-only token を発行する。
APIM 側の実装パターン
APIM では以下の処理を実装する必要がある。
validate-jwtで source token を検証- 必要なら source issuer / target route を operation 単位で固定
send-requestで broker を呼ぶ- 可能なら
authentication-managed-identityで APIM -> broker を保護
- 可能なら
- broker の response から
access_tokenを取り出し、backend call のAuthorizationheader に設定
以下のポリシー構成は上記実装の概念を表現したもの。
<inbound>
<validate-jwt header-name="Authorization" require-scheme="Bearer">
<openid-config url="https://source-idp.example/.well-known/openid-configuration" />
<audiences>
<audience>api://entry-api</audience>
</audiences>
</validate-jwt>
<send-request mode="new" response-variable-name="brokerResponse" timeout="20" ignore-error="false">
<set-url>https://broker.example/exchange</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<authentication-managed-identity resource="broker-app-id" />
<set-body>{ ... }</set-body>
</send-request>
<set-header name="Authorization" exists-action="override">
<value>@("Bearer " + (string)((IResponse)context.Variables["brokerResponse"]).Body.As<JObject>()["access_token"])</value>
</set-header>
</inbound>
このときの注意点は以下の2点。
- APIM policy に provider-specific 条件を詰め込みすぎないこと
- Credential Manager を generic broker の代わりにしないこと
Secret / identity の持ち方
この方式では secret と identity の扱いを十分に考慮する必要がある。broker の client secret / certificate をコードや環境変数に直書きせず、managed identity + Key Vault に寄せるべきである。
- Azure Functions / App Service、Azure Container Appsのいずれにおいても、managed identity を有効化し、Key Vault reference を利用できる。
- Key Vault 側では application-only access に managed identity を使うようRBACを構成する。
どの方式を第一候補にするべきか
実務上の優先順位は以下。
| target | 実装方式 |
|---|---|
| Auth0 | broker + Auth0 CTE を第一候補にしやすい。 最も heterogeneous token を受ける設計に寄せやすい。 |
| KeyCloak | same realm なら V2 cross-domain なら JWT Authorization Grant / chaining supported path と draft / preview 許容を切り分ける。 |
| Okta | broker は same-tenant Okta world の整理役としては使えるが、generic cross-IdP translator にはなり得ない。 |
| Entra | broker を置いても Entra OBO の前提は変わらないため、foreign token translator にはなり得ない。 |
基盤選定については、まずはACAを第一候補、ロジックが薄ければ Functions、Functions の開発モデルを保ったまま ACA の機能も使いたければ Functions on ACA が現実的。
アーキテクチャと運用上の考慮点
セキュリティ
- Custom Broker APIをホストするプラットフォーム(Functions、ACA)はネットワーク分離を推奨(VNet 統合、または APIM の outbound IP からのみアクセス許可)
- トークンは絶対にログへ出力しないこと
- すべての通信は TLS を使用
- 実現不可能ターゲットに対する fallback として refresh token を保存する場合は、保存時に暗号化とアクセス監査が必要
ユーザー コンテキストのトレードオフ
- ターゲット IdP が外部トークンを受け入れない場合(Entra、Okta ターゲット)、Custom broker APIは client credentials(app-only)へフォールバックせざるを得ないことがあるが、これにより per-user delegation semantics を失う。
- ユーザー アイデンティティ保持が必須であれば、保存済み refresh token を持つユーザー identity store を含む設計が必要で、re-consent 処理や revocation、保存セキュリティなどの運用複雑性が増す点に注意が必要。
スケーラビリティ
- FunctionsやACAはスケールするよう構成できる。
- 低レイテンシが必要なら FunctionsではPremium planもしくはFlex Consumption planで cold start を回避するよう構成する。ACAの場合でもインスタンスを少なくとも一つ常時動作するように構成しておくことを推奨する。
- トークン キャッシュ戦略はパフォーマンスに大きな影響を及ぼすので、十分に考慮すること。正確な値は実測で確認すべきであるが、token cache がない場合、すべての API 呼び出しで IdP B の token endpoint への round trip が増えるため、実装やネットワーク条件によっては 100〜250ms 程度以上のレイテンシ増分が発生し得る。
保守性
- Custom broker APIのコードは IdP 依存であり、IdP API の変更に追随する必要がある
- SDK バージョンを固定し、本番アップグレード前に sandbox で検証することを強く推奨
- token exchange 失敗率は運用上の健康指標として監視すること
そもそも Cross-IdP exchange が本当に必要か検討する(元も子もないけど)
- 実装前に、バックエンドが entry IdP のトークンをそのまま受け入れられないかを再評価してからでも遅くない。
- もし複数 IdP を使っている理由が組織境界であれば、SCIM などのディレクトリ同期と単一 IdP でのトークン発行へ寄せる方が、ランタイム token exchange よりも単純かつ堅牢な場合がある。
実装の順番
先に APIM だけで完結させようとすると、Cross-IdP の条件分岐と token exchange の例外系が policy XML に寄って複雑化しやすいため、先に Broker API 契約を固めてから APIM ポリシーを載せることを推奨する。これはアーキテクチャ案で記載した通り、「Broker API側で OBO を手実装し、APIM は gateway として組み合わせる」ことで、責務を分ける、ということ。
- Broker Function の request / response schema を決める。
- APIM → Broker 認証方式を決める(managed identity 推奨)。
- 1 つのターゲット IdP(たとえば Keycloak か Auth0)だけで token exchange を先に実装
Securing API to API calls in Azure with Entra and API Management – Rios Engineer
https://rios.engineer/securing-api-to-api-calls-in-azure-with-entra-and-api-management/
About Credential Manager in Azure API Management | Microsoft Learn
https://learn.microsoft.com/azure/api-management/credentials-overview - トークン キャッシュ戦略に基づき、Redis などのキャッシュを追加
- APIM に
validate-jwt+send-request+ backend header rewrite を載せる
Azure API Management policy reference – validate-jwt | Microsoft Learn
https://learn.microsoft.com/azure/api-management/validate-jwt-policy
Azure API Management policy reference – send-request | Microsoft Learn
https://learn.microsoft.com/azure/api-management/send-request-policy - Application Insights で成功率・失敗率・cache hit を可視化する
Resources
Azure API Management policy reference – validate-jwt | Microsoft Learn
https://learn.microsoft.com/azure/api-management/validate-jwt-policy
Azure API Management policy reference – send-request | Microsoft Learn
https://learn.microsoft.com/azure/api-management/send-request-policy
Azure API Management policy reference – authentication-managed-identity | Microsoft Learn
https://learn.microsoft.com/azure/api-management/authentication-managed-identity-policy
Azure API Management policy reference – get-authorization-context | Microsoft Learn
https://learn.microsoft.com/azure/api-management/get-authorization-context-policy
Azure Functions HTTP trigger | Microsoft Learn
https://learn.microsoft.com//azure/azure-functions/functions-bindings-http-webhook-trigger
Azure Functions Scale and Hosting | Microsoft Learn
https://learn.microsoft.com/azure/azure-functions/functions-scale
Managed Identities – Azure App Service | Microsoft Learn
https://learn.microsoft.com/azure/app-service/overview-managed-identity
Azure Container Apps overview | Microsoft Learn
https://learn.microsoft.com/azure/container-apps/overview
Ingress in Azure Container Apps | Microsoft Learn
https://learn.microsoft.com/azure/container-apps/ingress-overview
Scaling in Azure Container Apps | Microsoft Learn
https://learn.microsoft.com/azure/container-apps/scale-app
Managed identities in Azure Container Apps | Microsoft Learn
https://learn.microsoft.com/azure/container-apps/managed-identity
Manage secrets in Azure Container Apps | Microsoft Learn
https://learn.microsoft.com/azure/container-apps/manage-secrets
Azure Functions on Azure Container Apps overview | Microsoft Learn
https://learn.microsoft.com/azure/container-apps/functions-overview
Authenticate to Azure Key Vault | Microsoft Learn
https://learn.microsoft.com/azure/key-vault/general/authentication
Microsoft identity platform and OAuth2.0 On-Behalf-Of flow – Microsoft identity platform | Microsoft Learn
https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow
Set up OAuth 2.0 On-Behalf-Of Token Exchange | Okta Developer
https://developer.okta.com/docs/guides/set-up-token-exchange/-/main/
Custom Token Exchange – Auth0 Docs
https://auth0.com/docs/authenticate/custom-token-exchange
Configure Custom Token Exchange – Auth0 Docs
https://auth0.com/docs/authenticate/custom-token-exchange/configure-custom-token-exchange
Example Use Cases – Auth0 Docs
https://auth0.com/docs/authenticate/custom-token-exchange/cte-example-use-cases
Configuring and using token exchange – Keycloak
https://www.keycloak.org/securing-apps/token-exchange
JWT Authorization Grant – Keycloak
https://www.keycloak.org/securing-apps/jwt-authorization-grant
OAuth Identity and Authorization Chaining Across Domains
https://github.com/keycloak/keycloak/blob/main/docs/guides/securing-apps/oauth-identity-authorization-chaining-across-domains.adoc
RFC 8693: OAuth 2.0 Token Exchange
https://www.rfc-editor.org/rfc/rfc8693