デモ利用者がメール OTP で登録できる認証基盤を Microsoft Entra External ID で構築した

データを登録して後から確認するデモを考えていたとき、「登録した本人だけが自分のデータを見られる状態」が必要になりました。
この要件には認証が必要です。ただし、デモを使う人を管理者が毎回登録する運用にはしたくありません。利用者が自分で登録でき、パスワードを新しく作る必要もない構成にしたかったため、Microsoft Entra External ID の外部テナントとメールのワンタイムパスコード(OTP)を採用しました。
この記事では、実際に外部テナントを作り、React SPA と Azure Functions API を接続するまでに行った設定をまとめます。テナント ID、アプリケーション(クライアント)ID、Azure サブスクリプション、ホスト名などの環境固有情報は、すべてプレースホルダーへ置き換えています。
目次
- 先に結論
- 今回の要件と構成
- 構築前に決めたこと
- 外部テナントを作成する
- Web API をアプリ登録する
- SPA をアプリ登録する
- サインアップとサインインのユーザーフローを作成する
- アプリへ認証設定を反映する
- 本人のデータだけを返す
- 動作確認
- つまずいたポイント
- 作業後に確認すること
- まとめ
- 公式情報源
先に結論
今回の構成では、デモ利用者のアカウントを社員用の Microsoft Entra ID テナントに混在させず、External ID の外部テナントへ分離できました。デモの利用者は、メールアドレスへ届く OTP を使って自分で登録できます。新しいパスワードを作って覚える必要もありません。
この構成では、フロントエンドと API を別々のアプリとして Microsoft Entra ID に登録します。SPA は Single Page Application の略で、今回の React で作ったブラウザアプリを指します。
| アプリ登録 | 役割 |
|---|---|
| SPA | 認証画面を呼び出し、サインイン後の戻り先となるリダイレクト URI を定義する。利用者として Web API を呼ぶためのアクセストークンを取得する |
| Web API | アクセストークンの宛先となる API と、access_as_user などのアクセス許可を定義する。受け取ったトークンが自分宛てで、必要な権限を持つか検証する |
つまり、External ID の認証画面を呼び出す主体は SPA です。Web API のアプリ登録は認証画面を呼ぶためではなく、SPA が要求する API の宛先とアクセス許可を Microsoft Entra ID に知らせるために使います。
構築に必要だった作業は、次の7つです。
- 作成元のディレクトリと Azure サブスクリプションを確認し、
Tenant Creatorを準備する - 外部テナントを作成し、そのディレクトリへ切り替える
- Web API と SPA を別々にアプリ登録する
- API に委任されたアクセス許可のスコープを公開し、SPA へ追加する
- メール OTP を使うサインアップとサインインのユーザーフローを作成する
- SPA をユーザーフローへ関連付ける
- SPA でアクセストークンを取得し、API でトークンとデータの所有者を検証する
大事なのは、認証を追加しただけでは、本人のデータだけに制限できないことです。API では、検証済みアクセストークンに含まれる変更されない ID をデータの所有者として保存します。データの読み書きもその ID で絞り込むことで、初めて本人のデータだけを扱えます。
External ID の用途や、社員用テナント、B2B コラボレーションとの違いは、Microsoft Entra External ID 入門(2026年版)で整理しています。
今回の要件と構成
今回作ったのは、デモの利用者が自分で登録してサインインするための認証基盤です。取引先を社員用テナントへゲストとして招待する B2B コラボレーションではありません。
| 項目 | 採用した構成 |
|---|---|
| 利用者 | デモへ自分で登録する一般利用者 |
| 認証基盤 | Microsoft Entra External ID の外部テナント |
| 認証画面 | Microsoft がホストするサインイン画面 |
| サインイン方法 | メール OTP |
| クライアント | React SPA、Authorization Code Flow + PKCE |
| API | Azure Functions の Web API |
| アクセス許可 | 委任された access_as_user スコープ |
| 所有者キー | アクセストークンの oid クレーム |
| 登録時の収集項目 | メールアドレスのみ |
| クライアントシークレット | SPA と API のどちらにも作成しない |
SPA と API は別々にアプリ登録しました。SPA はサインインした利用者として API を呼び出します。API は、自分宛てに発行されたアクセストークンだけを受け入れます。
構築前に決めたこと
ポータルを操作する前に、次の値と運用担当を決めました。
| 項目 | 判断したこと |
|---|---|
| 作成元ディレクトリ | サブスクリプションを所有し、テナント作成が許可されたディレクトリ |
| 課金先 | 外部テナントに関連付けるサブスクリプションとリソースグループ |
| テナント名 | 管理画面で用途を識別できる名前 |
| ドメイン名 | <TENANT_SUBDOMAIN>.onmicrosoft.com のサブドメイン |
| 国/地域 | Japan。作成後に変更できない |
| Go-Local | 今回は利用しない |
| アプリ名 | SPA と API を識別できる別々の名前 |
| リダイレクト URI | ローカル環境と本番環境で SPA が実際に使う URI |
| 運用担当 | アプリ、ユーザーフロー、デモ利用者のアカウント、緊急復旧の担当者 |
2026年8月14日時点の公式手順では、外部テナントを作成するには、Microsoft Entra の組み込みディレクトリロールである Tenant Creator が必要です。これは Azure RBAC ロールではありません。ただし、公式手順では Tenant Creator を Azure サブスクリプションまたはリソースグループのスコープで割り当てます。
一方、Owner は Azure サブスクリプションなどのリソースを管理する Azure RBAC ロールです。2つは異なるロール体系のため、Azure サブスクリプションの Owner があっても、Tenant Creator がなければ外部テナントを作成できません。
国/地域は後から変更できません。また、Japan を選ぶと、有料の Go-Local データ所在地オプションが表示されます。正式導入前に、組織の要件と公式のデータ所在地情報を確認します。
外部テナントを作成する
外部テナントは Microsoft Entra 管理センターから作成しました。
- 作成元ディレクトリの管理者アカウントで Microsoft Entra 管理センターへサインインする
- Entra ID、概要、テナントの管理を開く
- 作成を選び、テナントの種類で 外部を選択する
- Azure サブスクリプションを使用を選択する
- テナント名、ドメイン名、国/地域を入力する
- サブスクリプションとリソースグループを選択する
- 確認と作成で内容を確認して作成する

公式ドキュメントでは、作成に最大30分かかる場合があると案内されています。作成後は、設定、ディレクトリとサブスクリプションから新しい外部テナントへ切り替えます。
テナントの概要で、後からアプリ設定に使う値を控えます。
Tenant name : <EXTERNAL_TENANT_NAME>
Primary domain : <TENANT_SUBDOMAIN>.onmicrosoft.com
Tenant ID : <EXTERNAL_TENANT_ID>

テナント ID やクライアント ID はシークレットではありませんが、環境の特定につながる値です。公開資料では実値を使いません。また、クライアントシークレット、アクセストークン、OTP、利用者のメールアドレスは記録しません。
Web API をアプリ登録する
先に、SPA から呼び出される Web API を登録しました。
- 外部テナントへ切り替えた状態で、Entra ID、アプリの登録、新規登録を開く
- API の用途が分かる名前を入力する
- この外部テナント内のアカウントだけを対象として登録する
- API の公開で Application ID URI を
api://<API_CLIENT_ID>にする - スコープの追加から、利用者として API を呼び出すための委任スコープを作成する

今回のスコープは次のようにしました。
| 項目 | 値 |
|---|---|
| Scope name | access_as_user |
| Who can consent | Admins and users |
| Admin consent display name | Access the demo API |
| User consent display name | Access the demo API |
| State | Enabled |
SPA が要求するスコープの完全名は次の形です。
api://<API_CLIENT_ID>/access_as_user
今回は、API が SPA から受け取ったアクセストークンを検証するだけなので、API のアプリ登録にクライアントシークレットは作成していません。
クライアントシークレットは、サーバー側のアプリが Microsoft Entra ID に対して「このアプリ自身からの要求である」と証明し、トークンを取得するときに使う資格情報です。たとえば、利用者がサインインしないバッチ処理やバックエンド間通信で client credentials flow を使う場合、サーバー側の Web アプリが認証処理を行う場合、API が利用者の代理で別の API を呼び出す On-Behalf-Of flow などで必要になります。
ただし、Azure 上で動くアプリでは、保存や定期更新が必要なクライアントシークレットより、マネージド ID や証明書などの漏えいしにくい方法を優先します。
SPA をアプリ登録する
次に、API を呼び出す SPA を登録しました。
- アプリの登録、新規登録を開く
- SPA の用途が分かる名前を入力する
- この外部テナント内のアカウントだけを対象として登録する
- 認証、プラットフォームの追加、シングルページ アプリケーションを選択する
- ローカル環境と本番環境のリダイレクト URI を登録する
http://localhost:<LOCAL_PORT>
https://<APP_HOST>/auth/callback
リダイレクト URI は、スキーム、ホスト、ポート、パス、末尾のスラッシュまで、SPA が送る redirectUri と一致させます。SPA はブラウザへ配信されるため、シークレットを安全に保持できません。コードへ埋め込んでも利用者から確認できてしまうため、SPA にはクライアントシークレットを作成しません。
続いて、API のアクセス許可、アクセス許可の追加、所属する組織で使用している APIから先ほどの API を選び、委任されたアクセス許可の access_as_user を追加します。Microsoft Graph を利用しない場合は、既定で追加された User.Read が必要かどうかも確認します。
サインアップとサインインのユーザーフローを作成する
メール OTP を利用するため、最初に External Identities、すべての ID プロバイダーを開き、テナント全体で Email One-time Passcode が有効になっていることを確認しました。

- External Identities、ユーザーフロー、新しいユーザーフローを開く
- ユーザーフローの名前を入力する
- ID プロバイダーで Email Accounts、Email one-time passcodeを選択する
- 利用者から収集する属性は Email Addressだけを選択して作成する
- 作成したユーザーフローの 使用、アプリケーションを開く
- アプリケーションの追加から SPA を選択する
1つのユーザーフローには複数のアプリを関連付けられます。一方、1つのアプリを関連付けられるユーザーフローは1つだけです。
アプリ一覧にある b2c-extensions-app は、外部テナントのカスタム属性を保持するために自動作成されるアプリです。今回カスタム属性を使わなくても削除しません。
アプリへ認証設定を反映する
OIDC の接続先は推測せず、外部テナントの OpenID configuration から issuer と jwks_uri を取得します。
https://<TENANT_SUBDOMAIN>.ciamlogin.com/<EXTERNAL_TENANT_ID>/v2.0/.well-known/openid-configuration
アプリへ設定する値は次のとおりです。
authority : https://<TENANT_SUBDOMAIN>.ciamlogin.com/<EXTERNAL_TENANT_ID>
clientId : <SPA_CLIENT_ID>
scope : api://<API_CLIENT_ID>/access_as_user
audience : <API_CLIENT_ID>
issuer : <OPENID_CONFIGURATION_ISSUER>
jwksUri : <OPENID_CONFIGURATION_JWKS_URI>
SPA は Authorization Code Flow + PKCE でアクセストークンを取得します。API は OpenID configuration から得た署名鍵と発行者情報を使い、少なくとも署名と iss、tid、aud、exp、nbf、scp を検証します。
API へ送るのは、その API 向けに発行されたアクセストークンです。ID トークンは、SPA が利用者のサインイン状態を確認するためのものです。API のアクセス制御には使いません。
本人のデータだけを返す
サインインに成功しただけでは、利用者 A が利用者 B のデータを読めないことまでは保証されません。API 側でも、誰のデータなのかを確認する必要があります。
今回は、検証済みアクセストークンの oid クレームを所有者 ID として保存しました。
ownerId = verifiedAccessToken.oid
データを登録するときは、リクエスト本文で送られた所有者 ID を信用しません。検証済みトークンから取得した oid をデータに保存します。取得、更新、削除でも、指定されたデータ ID だけで検索せず、必ず ownerId を検索条件へ追加します。
resourceId = <REQUEST_RESOURCE_ID>
ownerId = verifiedAccessToken.oid
メールアドレスは変更される可能性があるため、所有者キーには使いません。oid はテナント内で一意の値です。複数のテナントからトークンを受け入れる場合は、テナントを表す tid と組み合わせて扱います。今回は、1つの外部テナントから発行されたトークンだけを API で許可します。
動作確認
正常系では、次の流れを確認しました。
- 未登録のテスト用メールアドレスでサインアップを開始する
- 受信した OTP で登録し、SPA へ戻れることを確認する
- サインアウト後、同じアカウントでサインインできることを確認する
- SPA が
access_as_userスコープのアクセストークンを取得できることを確認する - API がトークンを検証し、本人のデータだけを返すことを確認する
- サインアウト後に認証キャッシュが削除され、保護された画面へ戻れないことを確認する
拒否系もあわせて確認します。
- トークンがない API リクエストを
401 Unauthorizedで拒否する - 発行者、テナント、対象 API、スコープが異なるトークンや、期限切れのトークンを拒否する
- ID トークンを API のアクセストークンとして受け入れない
- 別の利用者がデータ ID を指定しても、本人以外のデータを返さない
- ログ、Application Insights、ブラウザストレージにトークン、OTP、メールアドレスを残さない
つまずいたポイント
外部テナントの作成ボタンが無効になる
今回、Azure RBAC の Owner があっても、外部テナントを作成できませんでした。Microsoft Entra の組み込みディレクトリロールである Tenant Creator は、Owner とは別に必要です。確認すべきなのは、次の4点です。
- ポータルで現在選択しているディレクトリ
- 作成元ディレクトリでテナント作成が許可されているか
- Microsoft Entra ロールの
Tenant Creatorが割り当てられているか Tenant Creatorのスコープと、対象のサブスクリプションまたはリソースグループへのアクセス
複数のディレクトリへ参加しているアカウントでは、権限だけでなく、画面右上でどのディレクトリを選択しているかも確認します。
redirect URI mismatch になる
SPA のオリジンと /auth/callback を混在させず、認証ライブラリの redirectUri とアプリ登録の値を文字単位で比較します。localhost のポート、HTTP / HTTPS、末尾のスラッシュも確認します。
API がアクセストークンを受け取っても 401 になる
次の順序で確認します。
- ID トークンではなくアクセストークンを送っているか
audが API のクライアント ID かscpにaccess_as_userが含まれるかissとtidが許可した外部テナントの値か- OpenID configuration から得た署名鍵を使っているか
トークンの中身や個人情報を、公開記事、Issue、作業ログへ貼り付けないようにします。
作業後に確認すること
PoC の動作確認後も、認証基盤として運用するための作業が残ります。
- 外部テナント作成のためだけに付与した
Tenant Creatorを削除する - 日常管理者を最小権限のロールへ分離し、MFA と定期的なレビューを適用する
- MFA で保護したクラウド専用の緊急管理アカウントを複数用意する
- 不要なアクセス許可、リダイレクト URI、テスト用アカウントを削除する
- デモ利用者のアカウントとアプリデータを削除する手順、本人確認、監査方法を決める
- Conditional Access、サインインリスクの監視、レート制限、CSP を本番要件に合わせる
- カスタムドメインを使う場合は DNS、証明書、復旧手順を設計する
デモ API には、ディレクトリ内の利用者を削除できる Microsoft Graph のアクセス許可を付与していません。デモ利用者のアカウント削除は、管理者が行う別の手順に分けました。
まとめ
デモで登録した本人だけがデータを見られるようにするため、Microsoft Entra External ID の外部テナント、メール OTP のユーザーフロー、SPA と API のアプリ登録を構成しました。
External ID を使うと、利用者のセルフ登録とサインインをアプリから分離できます。ただし、サインインに成功しただけでは、本人のデータだけを返すことはできません。API でトークンを検証し、oid を所有者キーとしてすべてのデータ操作を制限する必要があります。
構築時は、現在選択しているディレクトリ、Tenant Creator、API のスコープ、SPA のアクセス許可、ユーザーフローとの関連付けを順番に確認すると、設定漏れを見つけやすくなります。
公式情報源
- Microsoft Entra External ID customer CIAM overview
- Create an external tenant
- Create a sign-up and sign-in user flow
- Add your application to the user flow
- Register an application
- Configure an application to expose a web API
- Configure a client application to access a web API
- Choose an authentication approach
- Validate claims in web APIs
- Microsoft Entra ID and data residency
- Microsoft Entra built-in roles