異なるIdP間のOBO flowをAzure API Managementと何かを組み合わせてなんとか実現したい

このエントリは2026/03/27現在の情報に基づいています。将来の機能追加や変更に伴い、記載事項からの乖離が発生する可能性があります。

このエントリは前回の続き。

前回の説明をしたところ、以下のような更問をもらった。

なるほど、状況は理解した。
では、Azure API Management (APIM) を中心にしつつ、他のコンポーネントを組み合わせてCross IdP OBO flowを実現することはできるか?できるとしたら、どのような方法が考えられるか?

各IdP固有のロジックを切り出して、Service calloutの形でAPIMから利用できればよさそうに見える。

推奨代替アーキテクチャ

前エントリに記載した6つの実現不可能な組み合わせおよび、6つの条件付き実現ケースを本番向けに堅牢化する場合には、Azure Functions(もしくはそれに類するもの。Container Appsでもよい) で実装するカスタム broker service を用意するのが最も現実的である。これは以下の目的・利得がある。

  • IdP 固有の交換ロジックを APIM から切り離す
  • メンテナンスをアプリケーション コード側に集中する
Step処理詳細
1Client → APIM(IdP A の JWT)クライアントは IdP A(例: Okta)で通常の OAuth 2.0 フローを通じて認証され、そのアクセス トークンを Authorization ヘッダーに入れて APIM 経由で API を呼び出す
2APIM が受信 JWT を検証APIM の <validate-jwt> ポリシーが IdP A の OIDC metadata(JWKS、issuer、audience)に対してトークンを検証する。
失敗した場合は HTTP 401 を返し、成功した場合は、ユーザー ID、メール、roles などの claim をコンテキスト変数へ抽出する。
3APIM が Azure Function broker に転送<send-request> またはバックエンド ルーティングを使って、APIM は検証済み IdP A トークン(または抽出済み claim)を Azure Function に送る。
APIM と Function 間の通信は managed identity、function key、mTLS などで保護する。
4Azure Function が Cross-IdP token acquisition を実行Function は後述する IdP B 固有ロジックを実行し、ユーザーを代理した新しい IdP B トークンを取得して APIM に返す。
5APIM が IdP B トークンでバックエンドに転送APIM は IdP B トークンを Authorization ヘッダーへ設定し、元の API リクエストをバックエンド サービスへ転送する。
バックエンドは IdP B トークンを検証し、処理を行う。

図にすると以下のよう。

Azure Function に必要なロジック

責務説明IdP ごとの考慮
受信トークンの検証IdP A の JWT を検証する(JWKS による署名、issuer、audience、expiry)。Entra:Microsoft.Identity.Web / JwtBearer middleware と OpenID metadata に基づく bearer token validation(MSAL は主として token acquisition 側で利用するライブラリ)
Okta:SDK または JWKS 手動取得
Auth0:SDK
Keycloak:OIDC metadata を利用。
ユーザー ID マッピングIdP A のユーザーを IdP B 側の対応ユーザーへマップする。DB、ディレクトリ同期、メール一致などの仕組みが必要。もし両 IdP へ同じユーザーが存在(たとえば SCIM 同期済み)していれば、UPN や email でマップ可能。直接対応がなければ service-account トークンへフォールバックし、ユーザー コンテキストは失われる。
IdP B からのトークン取得対象 IdP に応じて、ユーザーを表すトークンを取得する。Keycloak ターゲット
grant_type=token-exchangesubject_tokensubject_issuer で token endpoint を呼ぶ
Auth0 ターゲット
/oauth/tokengrant_type=token-exchangesubject_token を渡し、該当 profile にマッチさせる
Entra ターゲット(OBO 不可)
ユーザー コンテキストを維持したいなら、事前に取得して保存した refresh token による refresh grant を使う。そうでなければ client credentials(app-only、ユーザー コンテキストなし)
Okta ターゲット(OBO 不可)
同様に、client credentials か、保存済み refresh token による代替
トークン キャッシュIdP B のトークンをユーザー ID + scope ごとにキャッシュし、連続リクエストごとの余分な交換を避ける。単一インスタンスなら in-memory
スケールアウトなら Azure Managed Redis
Keycloak 側 token exchange のキャッシュ TTL は、ドキュメント中の例値に依存せず、実際に返ってきた expires_in を基準に安全側へ短めに設定する。
エラー処理IdP B endpoint の失敗(期限切れ、account not linked、scope 不正、rate limit など)を処理し、適切な HTTP ステータスを APIM に返す。Keycloak では、少なくとも external token が既存ユーザーへマップされるケースで account link がないと exchange が拒否され得る。not_linked はその代表的な失敗条件として理解するのが安全である。
Okta は、iss / aud / exp などの不整合で subject token is invalid / invalid_grant になる代表例がsupport KB で示されている。
Entra では、cross-issuer token を使った direct OBO は supported path が確認できず、要求条件を満たさない場合は token endpoint で失敗する(exact な error code / error_description は条件依存)。
クライアントには行動可能なエラーとして返す。
秘密情報管理IdP B の client secrets、証明書、保存済みユーザー資格情報などを安全に保持する。Azure Key Vault 参照を使う。
IdP ごとの推奨サイクルに従ってシークレットをローテーションする。

Entra / Okta ターゲット向け fallback 実装の考え方

Entra では、OBO の assertion に middle-tier 宛て Entra access token を要求しており、direct cross-issuer OBO の supported path は確認できない。Okta では、公式ガイドの token exchange は single custom authorization server または同一 tenant 内 trusted servers に限定されており、外部 issuer token の直接交換は documented path として確認できない。このため、Entra / Okta target の direct Cross-IdP OBO は、現時点では非サポート扱いで説明するのが最も正確である。

そのため Broker 実装では、少なくとも次の 2方法からどちらかを選択する必要がある。

  1. 保存済み refresh token を使う
    事前の interactive consent で得た refresh token をユーザーごとに安全に保存し、Broker で refresh grant を行う。
  2. client credentials fallback
    ユーザー コンテキストを捨てて app-only token を発行する。

ただし、いずれの方式も厳密な意味でのCross-IdP OBOではない点は注意が必要。

アーキテクチャと運用上の考慮点

セキュリティ

  • Azure Function はネットワーク分離を推奨(VNet 統合、または APIM の outbound IP からのみアクセス許可)
  • トークンは絶対にログへ出力しないこと
  • すべての通信は TLS を使用
  • 実現不可能ターゲットに対する fallback として refresh token を保存する場合は、保存時に暗号化とアクセス監査が必要

ユーザー コンテキストのトレードオフ

  • ターゲット IdP が外部トークンを受け入れない場合(Entra、Okta ターゲット)、Function は client credentials(app-only)へフォールバックせざるを得ないことがあるが、これにより per-user delegation semantics を失う。
  • ユーザー アイデンティティ保持が必須であれば、保存済み refresh token を持つユーザー identity store を含む設計が必要で、re-consent 処理や revocation、保存セキュリティなどの運用複雑性が増す点に注意が必要。

スケーラビリティ

  • Azure Functions の Consumption plan は自動スケールする。
  • 低レイテンシが必要なら Premium planもしくはFlex Consumption planで cold start を回避するよう構成する。
  • トークン キャッシュ戦略はパフォーマンスに大きな影響を及ぼすので、十分に考慮すること。token cache がない場合、すべての API 呼び出しで IdP B の token endpoint への round trip が増えるため、実装やネットワーク条件によっては 100〜250ms 程度以上のレイテンシ増分が発生し得る。正確な値は実測で確認すべきである。

保守性

  • Function コードは 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 Function の API 契約を固めてから APIM ポリシーを載せることを推奨する。これはつまり、「Functions 側で OBO を手実装し、APIM は gateway として組み合わせる」ことで、責務を分ける、ということ。

  1. Broker Function の request / response schema を決める。
  2. APIM → Broker 認証方式を決める(managed identity 推奨)。
  3. 1 つのターゲット IdP(たとえば Keycloak か Auth0)だけで token exchange を先に実装
    Securing API to API calls in Azure with Entra and API Management – Rios Engineer
    About Credential Manager in Azure API Management | Microsoft Learn
  4. Redis などのキャッシュを追加
  5. APIM に validate-jwt + send-request + backend header rewrite を載せる
    Azure API Management policy reference – validate-jwt | Microsoft Learn
    Azure API Management policy reference – send-request | Microsoft Learn
  6. Application Insights で成功率・失敗率・cache hit を可視化する

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください