企業ごとの Entra ID で使うマルチテナント Foundry チャット

同じ企業グループでも、グループ会社ごとに別々の Microsoft Entra ID テナントで社員を管理していることがあります。今回は、そうした複数の利用会社が、サービス提供会社の運用する1つの AI チャットを共通で使うケースを想定しました。
利用者をサービス提供会社の Microsoft Entra ID テナントへゲストとして集約するのではなく、利用会社が管理する社員アカウントをそのまま使います。そのために、組織アカウント向けのマルチテナント App Registration と、利用会社ごとのテナントに作成される Enterprise Application を組み合わせました。利用者の MFA や Conditional Access、退職時のアカウント停止といった ID 管理は利用会社に残しつつ、サービス提供会社の API で利用を許可する Tenant ID を制限します。
この記事では、Microsoft Foundry のチャットデモで実際に採用した構成、テナントを追加する手順、API 側で必要な検証、HTTP 403 でつまずいたポイントをまとめます。Tenant ID、Application ID、リソース名などの環境固有情報は、すべてプレースホルダーへ置き換えています。
目次
- 先に結論
- 今回の要件と構成
- App Registration と Enterprise Application の役割
- Microsoft Entra ID を構成する
- 許可するテナントを二段階で検査する
- 利用会社をオンボーディングする
- 利用者を利用会社ごとに限定する
- Foundry をキーなし、閉域で呼び出す
- External ID を採用しなかった理由
- 実装時につまずいたポイント
- 動作確認
- 本番化で追加検討すること
- まとめ
- 公式情報源
先に結論
今回満たしたかった要件は、次の 4 つです。
- サービス提供会社が運用する1つのシステムを、Microsoft Entra ID テナントが異なる複数の利用会社へ提供する
- 利用者は利用会社の Workforce tenant に所属する Member ユーザーのままとし、サービス提供会社のテナントへゲストユーザーを作らない
- MFA、Conditional Access、入退社に伴うアカウント管理は、それぞれの利用会社に残す
- サービス提供会社は利用を許可する利用会社を制限し、利用会社は自社内で利用できる社員やグループを制限できる
この要件には、Microsoft Entra External ID の外部テナントではなく、通常の Workforce tenant を対象にしたマルチテナント App Registration が合っていました。
サービス提供会社は App Registration を1つ所有します。利用会社がアプリへ同意すると、その利用会社のテナントに Service Principal が作成され、Microsoft Entra 管理センターでは Enterprise Application として管理できます。利用者自身は利用会社のテナントに所属する Member ユーザーのままで、サービス提供会社のテナントへ複製されません。
ただし、マルチテナントに設定しただけでは、任意の組織からの利用を防げません。今回の構成では、次の 3 層で利用範囲を制御しました。
| 制御する場所 | 制御する対象 | 今回の実装 |
|---|---|---|
| 利用会社のホームテナント | 本人確認とサインイン条件 | 利用会社の MFA、Conditional Access、アカウントライフサイクルを利用 |
| サービス提供会社の API | 利用を許可する組織 | 検証済みアクセストークンの tid を Tenant ID 許可リストと照合 |
| 利用会社の Enterprise Application | テナント内の利用者 | 必要に応じて割り当て必須を有効化し、ユーザーまたはグループを割り当て |
ブラウザでも Tenant ID を検査しましたが、これは未許可ユーザーに早く案内するための UX 改善です。セキュリティ境界は API 側のトークン検証と Tenant ID 検査に置きました。
Foundry への接続は、App Service に割り当てた User-assigned Managed Identity と Azure RBAC で認証し、Private Endpoint で到達経路を制限しました。利用者へ Foundry の API キーや Azure RBAC を渡す必要はありません。
今回の要件と構成
今回作ったのは、サービス提供会社が1つのシステムとして運用し、複数の利用会社がそれぞれ自社の Microsoft Entra ID アカウントでサインインできる軽量な AI チャットです。
| 項目 | 採用した構成 |
|---|---|
| 利用者 | 許可した利用会社の Workforce tenant に所属する社員 |
| Web アプリ | Azure App Service for Linux 上の ASP.NET Core 10 |
| フロントエンド | SPA、MSAL Browser、Authorization Code Flow with PKCE |
| API | SPA と同じ App Service で配信する POST /api/chat |
| 認証 | 組織アカウント向けのマルチテナント App Registration |
| API の委任スコープ | access_as_user |
| 組織の認可 | API で tid を Tenant ID 許可リストと照合 |
| モデル | Microsoft Foundry にデプロイした gpt-4o-mini |
| Foundry 認証 | User-assigned Managed Identity と Azure RBAC |
| Foundry ネットワーク | Public Network Access を無効化し、Private Endpoint 経由に限定 |
| 監視 | Application Insights と Log Analytics |
通信の流れは次のとおりです。
- 利用者が所属する利用会社の Microsoft Entra ID でサインインする
- 利用会社のテナントが MFA や Conditional Access を評価する
- SPA が Web API 向けのアクセストークンを取得する
- API が署名、issuer、audience、有効期限、
access_as_user、tidを検証する - App Service が Managed Identity で Foundry 用のトークンを取得する
- App Service の VNet 統合と Private Endpoint を経由してモデルを呼び出す
Web アプリだけをインターネットへ公開し、Foundry のデータプレーンはパブリックネットワークから到達できない構成にしました。
App Registration と Enterprise Application の役割
この構成を理解するうえで重要なのが、App Registration と Enterprise Application の違いです。
Microsoft の公式ドキュメントでは、Application object はアプリのグローバルな定義、Service Principal は各テナントにおけるローカルな実体と説明されています。Application object と Service Principal は 1 対多の関係です。
| オブジェクト | 作成される場所 | 役割 |
|---|---|---|
| Application object | サービス提供会社の所有テナント | リダイレクト URI、公開スコープ、対応するアカウント種別など、アプリ共通の定義を持つ |
| Service Principal | アプリを利用する利用会社ごとのテナント | 同意、ユーザー割り当て、サインイン制御など、そのテナント固有の設定を持つ |
| Enterprise Application | Microsoft Entra 管理センター上の表示 | テナント内の Service Principal を管理する画面 |
利用会社の管理者が同意すると、その利用会社のテナントに Service Principal が作られます。これによって利用会社は、自社の Enterprise Application に対して利用者の割り当てやサインインの無効化を行えます。
ここで作られるのはアプリの実体であり、社員ユーザーのコピーではありません。利用者は自社テナントの Member ユーザーとしてサインインし続けます。
Microsoft Entra ID を構成する
サービス提供会社の Azure 管理テナントに、アプリ登録を1つ作成しました。
| 設定 | 値 |
|---|---|
| Name | 利用者が識別しやすいアプリ名(例:Group Corp AI Chat Demo) |
| Supported account types | Multiple Entra ID tenants |
signInAudience | AzureADMultipleOrgs |
| Redirect URI のプラットフォーム | Single-page application (SPA) |
| SPA Redirect URI | MSAL の redirectUri と完全一致する https://<APP_HOST>/ |
| API Identifier URI | api://<APPLICATION_ID> |
| 委任スコープ | access_as_user |
| Who can consent? | Admins and users |
| API permissions | 自身の API の委任された access_as_user |
| MSAL authority | https://login.microsoftonline.com/organizations |
| クライアントシークレット | 作成しない |
現行の Microsoft Entra 管理センターでは、アプリ登録時の Supported account types に Multiple Entra ID tenants を選択します。これはマニフェストの signInAudience では AzureADMultipleOrgs に対応します。個人用 Microsoft アカウントまで許可する Any Entra ID Tenant + Personal Microsoft accounts は、利用会社の組織アカウントだけを対象にする今回の要件では選びません。
登録画面で Redirect URI を入力する場合は、プラットフォームに Single-page application (SPA) を選びます。ここは省略して登録し、後から Authentication で追加しても構いません。どちらの場合も、末尾の / やパスを含めて、SPA が認証要求に指定する Redirect URI と完全に一致させます。
SPA が要求するスコープは次の形です。
api://<APPLICATION_ID>/access_as_user
Expose an API でスコープを公開した後、API permissions、Add a permission、My APIs の順に開き、同じアプリの Delegated permissions から access_as_user を追加します。これによって、アプリが基本動作に必要とするアクセス許可として access_as_user が登録され、利用会社の管理者が同意内容を確認できます。アクセス許可を後から変更した場合、すでに同意した利用者やテナントでも再同意が必要になることがあります。
SPA はブラウザで動作する public client であり、クライアントシークレットを安全に保持できません。Authorization Code Flow with PKCE を使い、サインインした利用者として API 向けのアクセストークンを取得しました。
organizations authority は、任意の Microsoft Entra 組織アカウントでサインインできる入口です。ここに接続しただけでは、利用を許可する利用会社の一覧は表現できません。
許可するテナントを二段階で検査する
今回の API は、Microsoft.Identity.Web を使ってアクセストークンを検証しました。
- JWT の署名
- issuer
- audience
- 有効期限
- 委任スコープ
access_as_user - 許可された Tenant ID
Tenant ID の許可リストは App Service の設定へ配列として渡しました。
AllowedTenantIds__0=<TENANT_ID_A>
AllowedTenantIds__1=<TENANT_ID_B>
AllowedTenantIds__2=<TENANT_ID_C>
API では、フレームワークが検証したアクセストークンの Tenant ID クレームを許可リストと照合し、未許可なら HTTP 403 を返します。
スコープは Microsoft.Identity.Web の標準機能を使って検証しました。
app.MapPost("/api/chat", ChatAsync)
.RequireAuthorization()
.RequireScope("access_as_user");
Microsoft identity platform の公式ドキュメントでは、マルチテナント API がトークンを検証するとき、tid と iss を結び付け、署名鍵の issuer も確認する必要があると説明されています。単に JWT をデコードし、クレームの文字列だけを信頼してはいけません。
SPA でも、MSAL のサインイン結果に含まれる account.tenantId を確認しました。未許可組織ならアクティブアカウントを設定せず、チャット入力欄を有効にしません。ただし、ブラウザ側の処理は変更できるため、API 側の検査を省略する理由にはなりません。
トークン発行前に未許可組織を止める必要がある場合は、サインイン前に利用会社を選択させ、許可した Tenant ID 固有の authority へ誘導する設計も検討できます。
利用会社をオンボーディングする
新しい利用会社を追加するときは、次の手順で進めました。
- 利用会社の Tenant ID を確認する
- Tenant ID を Web アプリの
AllowedTenantIdsへ追加する - 対象テナントの管理者が
access_as_userへ同意する - 対象テナントに Enterprise Application が作成されたことを確認する
- 許可した利用会社のアカウントでサインインし、API 呼び出しを確認する
- 未許可テナントからの API 呼び出しが HTTP 403 になることを確認する
管理者同意 URL は次の形式です。
https://login.microsoftonline.com/<TENANT_ID>/v2.0/adminconsent
?client_id=<APPLICATION_ID>
&scope=api%3A%2F%2F<APPLICATION_ID>%2Faccess_as_user
&redirect_uri=<URL_ENCODED_REDIRECT_URI>
実際の URL では改行を除きます。管理者同意は強い操作なので、利用会社の管理者に要求するスコープと用途を確認してもらいます。Microsoft の公式ドキュメントでも、テナント全体の管理者同意は、要求されるアクセス許可を慎重にレビューしてから行うよう案内されています。
利用者を利用会社ごとに限定する
Tenant ID の許可リストだけでは、許可した利用会社の中で誰が利用できるかまでは絞りません。特定の社員やグループだけに限定する場合は、利用会社ごとのテナントにある Enterprise Application で設定します。
- Microsoft Entra 管理センターで Entra ID、Enterprise applications を開く
- 対象アプリの Properties を開く
- Assignment required? を Yes にする
- Users and groups から利用者またはグループを割り当てる
- 割り当てたユーザーと未割り当てユーザーの両方で結果を確認する
割り当て必須を有効にすると、割り当てられていない利用者はサインインやアクセストークンの取得を拒否されます。また、公式ドキュメントでは、この設定を有効にしたアプリではユーザー同意が許可されないため、テナント全体の管理者同意が必要になると説明されています。
アプリ独自の役割まで API で強制したい場合は、App Registration にアプリロールを定義し、アクセストークンの roles クレームを検証します。その場合は、利用会社ごとの Enterprise Application で利用者やグループへのロール割り当ても必要です。
今回のデモではテナント横断の動作確認を優先し、API の必須条件は access_as_user と Tenant ID 許可リストにしました。利用者単位の制御は利用会社ごとの Enterprise Application で追加できる形です。
Foundry をキーなし、閉域で呼び出す
エンドユーザーの認証と、App Service から Foundry への認証は分けて考えます。
エンドユーザーのアクセストークンは Web API を呼ぶためのものです。Foundry の呼び出しには転送せず、App Service に割り当てた User-assigned Managed Identity を使いました。
- App Service に User-assigned Managed Identity を割り当てる
- Foundry リソースでローカルキー認証を無効化する
- Managed Identity に
Cognitive Services OpenAI Userロールを割り当てる - アプリから
DefaultAzureCredentialで Microsoft Entra トークンを取得する - Foundry の Public Network Access を無効化する
- App Service を専用サブネットへ VNet 統合する
- 別サブネットに Foundry の Private Endpoint を作成する
- 必要な Private DNS Zone と VNet Link を構成する
Microsoft の公式ドキュメントでは、Foundry Models へのキーレス認証に Microsoft Entra ID と Azure RBAC を使い、互換性を確認したうえでキー認証を無効化する方法が案内されています。また、App Service の VNet 統合はアプリから VNet への送信アクセスを提供します。
Managed Identity は「どのワークロードが呼べるか」、Private Endpoint は「どのネットワーク経路から到達できるか」を制御します。片方だけで代用せず、認証とネットワークの両方を組み合わせました。
この構成では、エンドユーザーへ次の情報や権限を渡しません。
- Foundry の API キー
- Azure RBAC
- Managed Identity
- Foundry エンドポイントへの直接アクセス
External ID を採用しなかった理由
Microsoft Entra External ID の外部テナントも検討しましたが、今回の利用者は一般消費者ではなく、利用会社の Workforce tenant に所属する社員です。
| 判断軸 | マルチテナント App Registration | External ID の外部テナント |
|---|---|---|
| 主な利用者 | 他社を含む Microsoft Entra 組織アカウント | 自社アプリを使う顧客や一般利用者 |
| ユーザーの所在 | 利用会社の Workforce tenant に残る | External tenant 内に顧客ユーザーを作成する |
| 利用会社の MFA や Conditional Access | 利用会社のホームテナントで運用しやすい | External tenant の顧客向けポリシーとして別設計する |
| 利用会社ごとの利用者制御 | 利用会社の Enterprise Application で割り当てる | External tenant 内で一元管理する |
| 今回の要件との相性 | 利用会社の ID をそのまま使う要件に合う | 別ディレクトリへユーザーを作るため合わない |
External ID が劣っているわけではなく、対象者と ID の管理主体が異なります。セルフサービスで顧客を登録するアプリなら External ID、既存の組織アカウントを利用会社の管理下で使うならマルチテナント App Registration、という切り分けにしました。
External ID の用途と実装は、Microsoft Entra External ID 入門(2026年版)とメール OTP で登録できる認証基盤を構築した記事で詳しくまとめています。
実装時につまずいたポイント
認証後に HTTP 403 になる
サインインと JWT 検証は成功しているのに、チャット API へ入る前に HTTP 403 になりました。原因は 2 つありました。
- API が
Chat.Userアプリロールを必須にしていたが、利用者のホームテナント側でロールを割り当てていなかった - 生の
scpとtidクレームだけを参照し、Microsoft.Identity.Web のクレームマッピングを考慮していなかった
デモでは必須条件を access_as_user と Tenant ID 許可リストに整理し、スコープ検証には Microsoft.Identity.Web の RequireScope を使いました。Tenant ID は、実際にフレームワークが生成した Claim 一覧を確認してから取得方法を決めました。
設定へ追加した Tenant ID が反映されない
許可リストを App Service の設定へ追加しても、稼働中のアプリが古い一覧を返し続けることがありました。
配列全体を一括で保存して App Service を再起動し、実行時の GET /api/config で読み込まれた Tenant ID を確認して解消しました。設定値そのものだけでなく、アプリが実際に読み込んだ値を確認できる診断 API が役立ちました。
診断 API で Tenant ID を返す場合は、認証済みの管理者だけに限定する、値をマスクする、または検証後に無効化するなど、環境情報の露出を抑える必要があります。
Foundry とモデルのデプロイが競合する
Bicep を再適用した際、Foundry アカウントが更新中のままモデル子リソースの更新が始まり、AccountProvisioningStateInvalid になりました。
モデルデプロイを Foundry project の完了後へ明示的に依存させ、アカウントとモデルの更新を直列化して解消しました。
リージョンをモデルの可用性だけで決められない
当初選んだリージョンでは、対象サブスクリプションの App Service Total Regional VMs クォータが 0 でした。モデルをデプロイできても Web アプリを配置できないため、App Service とモデルの両方を利用できるリージョンへ変更しました。
デプロイ前に、モデルのリージョンとクォータだけでなく、App Service Plan のリージョンクォータも確認します。
動作確認
今回のデモでは、次の項目を確認しました。
- Bicep のコンパイルが成功する
- ASP.NET Core の Release ビルドが成功する
- App Service のトップページと
GET /healthzが HTTP 200 を返す - 未認証の
POST /api/chatが HTTP 401 を返す - 未許可テナントの UI でチャット入力欄が有効にならない
- 未許可テナントのアクセストークンで API が HTTP 403 を返す
- 許可 Tenant ID が実行時設定へ反映される
- Foundry の Public Network Access とローカル認証が無効になっている
- Private Endpoint 接続が Approved になっている
- Foundry のホスト名が App Service からプライベート IP へ解決される
- Managed Identity に
Cognitive Services OpenAI Userが割り当てられている - App Service の VNet 統合が有効になっている
- Application Insights で HTTP 結果、例外、遅延を確認できる
特に、許可した利用者が成功するテストだけでなく、未認証、未許可テナント、未割り当てユーザーが拒否されることも確認します。
本番化で追加検討すること
今回のデモでは、RAG、ファイルアップロード、会話履歴の永続化を対象外にしました。本番へ進める場合は、少なくとも次を追加で検討します。
- Front Door Premium または Application Gateway WAF
- カスタムドメインと証明書
- 利用会社ごとの Enterprise Application の割り当て必須化
- アプリロールによる細粒度な認可
- 利用者単位のリクエスト数、トークン数、同時実行数の制限
- プロンプトと回答を記録する場合の通知、保持期間、閲覧権限
- モデル更新時の評価データセットと品質ゲート
- 可用性ゾーン、複数インスタンス、障害復旧手順
- CI/CD と IaC 変更の承認フロー
- 利用終了時の Enterprise Application、同意、許可 Tenant ID の削除手順
複数の利用会社を受け入れる構成では、追加手順だけでなく、契約終了や組織再編時のオフボーディング手順も最初から用意しておく必要があります。
まとめ
サービス提供会社と利用会社の Microsoft Entra ID テナントが異なっていても、マルチテナント App Registration を使えば、利用者をサービス提供会社のテナントへ複製せずに、複数の利用会社へ1つのシステムを提供できます。
設計上の要点は次の 5 つです。
- App Registration はサービス提供会社が所有し、利用会社ごとに Service Principal が Enterprise Application として作られる
- 利用者は利用会社の Member ユーザーのままで、利用会社の MFA、Conditional Access、アカウント管理を利用する
organizationsauthority だけに頼らず、API で検証済みトークンのtidを許可リストと照合する- 個別ユーザーの制限は利用会社の Enterprise Application で割り当て必須を有効化する
- Foundry は Managed Identity による認証と Private Endpoint によるネットワーク制御を組み合わせる
「外部ユーザーだから External ID」と決めるのではなく、既存の組織 ID を誰が管理し続けるべきかで方式を選ぶと、今回のように複数のグループ会社で1つのシステムを共通利用する場合でも、サービス提供会社と利用会社の責任境界を整理しやすくなります。
公式情報源
- Register an application in Microsoft Entra ID
- Application and service principal objects in Microsoft Entra ID
- Access tokens in the Microsoft identity platform
- Convert single-tenant app to multitenant on Microsoft Entra ID
- Grant tenant-wide admin consent to an application
- Restrict a Microsoft Entra app to a set of users
- Configure keyless authentication with Microsoft Entra ID
- Integrate your app with an Azure virtual network
- Use private endpoints for Azure Apps