Azure AD B2C から Entra External ID に移行したら、「メール+パスワード」でサインインしたのに ID トークンに email クレームが入らない……という相談が一気に増えています。この記事では、Microsoft Q&A で公式に紹介された解決策をベースに、ユーザーフロー設定だけで email を返す最短ルートから、B2C カスタムポリシー相当の考え方、どうしても出ない場合のワークアラウンドまで、実務でそのまま使える形で整理します。
Entra External ID と email クレームの前提知識
まずは、Azure AD B2C 時代との違いと、なぜ email が「出たり出なかったり」するのかを整理します。
Azure AD B2C と Entra External ID の関係
現在の「Entra External ID」は、ざっくり次の2つを内包しています。
- 外部テナント(CIAM):新しいカスタマー向け Entra テナント。メール / OTP / Google / Facebook などでサインインさせる、いわゆる「Entra External ID for customers」。ユーザーフロー主体で、カスタムポリシー(IEF XML)は非対応。
- 旧 Azure AD B2C テナント:レガシー扱いだがまだ利用可能。こちらは従来どおり Identity Experience Framework(IEF)のカスタムポリシーが使える。
ドキュメントや Q&A では両者をまとめて「Entra External ID」と呼ぶことが多く、混乱の元になりがちです。
なぜ email クレームが出ないのか(ざっくり要約)
2023 年以降、メール アドレス クレームを ID の主キーとして使うことは危険という前提で、Microsoft は挙動をかなり変えました。いわゆる「nOAuth」問題への対策です。
- マルチテナント アプリなどでは、ドメイン所有者が検証していないメール アドレスはトークンから削られるのが既定。
- 「ドメイン所有者が検証済み」とみなされるケースの一つとして、OTP(ワンタイムパスコード)でメールを使ってサインインした場合がある。
- 結果として、同じユーザーフローでも「メール+パスワード」だと
emailが出ない一方、「メール OTP」だとemailが出る、という現象が起きやすくなっています(Microsoft Q&A やコミュニティでも多数報告あり)。
一方で、アプリ側としては 「ユーザーを一意に識別する ID」としてではなく、ユーザープロファイル情報として email が欲しいだけ、というケースも多いはずです。そこで、安全な前提を守りつつ email クレームを返すためのパターンを見ていきます。
最短ルート:ユーザーフローのクレーム設定で email を返す
まずは、カスタムポリシーを使わずに完結する「王道パターン」から整理します。Microsoft Q&A で公式回答として紹介されている手順の要約+補足です。
Entra ポータルで「メール アドレス」クレームを有効化
前提として、「外部 ID > ユーザーフロー」を使った サインアップ/サインイン フローを利用しているケースを想定します。
- Entra 管理センターにサインインします。
Microsoft Entra ID > 外部 ID > ユーザーフローを開き、利用中の サインアップ/サインイン フロー(例:SignUpSignIn) を選択します。- 左メニューから [アプリケーション クレーム](または [トークン クレーム] や [出力クレーム] といった名称)を開きます。
- クレーム一覧から [メール アドレス] を探し、次を確認します。
- クレームが 有効になっている。
- ID トークンに含める設定になっている(API 側で必要なら アクセス トークンにもチェック)。
- 保存してユーザーフローを再実行します。
多くのケースでは、この設定だけで ID トークンに email(および email_verified に相当するクレーム)が入るようになります。
アプリ側の OIDC リクエストで scope=email を付ける
OpenID Connect を使っている場合、scope に email が含まれていないと email クレームが出ない構成があります。
典型的な認可リクエストは次のようになります。
GET https://<tenant>.ciamlogin.com/<tenant-id>/oauth2/v2.0/authorize
?client_id=<client-id>
&response_type=code
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fsignin-oidc
&scope=openid%20profile%20email%20offline_access
&state=...
&code_challenge=...
&code_challenge_method=S256
ポイントは scope=openid profile email を明示することです。email スコープは「メール アドレスを含む追加情報へのアクセス」を委任すると解釈されます。
jwt.ms などで ID トークンを検査する
設定後は、必ずトークンの中身を確認しておきましょう。
- アプリからサインイン フローを流し、発行された ID トークン をコピーします。
- jwt.ms に貼り付け、ペイロードを確認します。
- 次のようなクレームが入っているかチェックします。
email:メール アドレスemail_verifiedまたは同等の検証フラグ(テナントやアプリによって有無は異なる)preferred_username:メール相当が入ることが多い
ここで ID トークンではなくアクセス トークンを見てしまうミスが非常に多いので注意してください(後述の「落とし穴」参照)。
それでも email が出ないときに確認したいポイント集
設定を入れても email が見当たらない場合、多くは次のいずれかです。
よくある症状と原因の対応表
| 症状 | よくある原因 | 確認する場所 |
|---|---|---|
| ID トークンに email がない | ユーザーフローのクレーム設定漏れ / scope に email がない / メール未検証 | ユーザーフロー設定、アプリの認可リクエスト、ユーザー プロファイル |
| アクセス トークンには email がないが ID トークンにはある | メール アドレス クレームを「ID トークンのみに含める」設定にしている | ユーザーフローの「トークンクレーム」設定 |
| どのトークンにも email がない | マルチテナント アプリ+未検証メールで自動的に削除されている | Graph の authenticationBehaviors 設定、ユーザーのメール ドメイン |
| OTP では email が出るが、パスワードでは出ない | OTP サインイン時のみ「ドメイン所有者検証済み」と扱われている可能性(後述の推測) | サインイン方法の違い、アプリのテナント構成 |
| トークンには email があるのに、アプリ側コードから取れない | クレーム名のマッピング / シリアライザ設定ミス | .NET / Node などクライアントのクレーム処理コード |
ID トークン vs アクセス トークンの取り違え
最初に疑うべきはここです。
- ブラウザー側のフロントエンドは、OIDC ログイン直後に ID トークン を受け取り、それをクッキーやセッションに流し込むことが多い。
- Web API の認可に使うのは通常 アクセス トークン。
ライブラリによっては「ID トークンしかログに出ていない」「アクセス トークンしか保存していない」ということもあります。まずは どちらのトークンを確認しているのかを意識して下さい。
scope=email / profile の付け忘れ
Microsoft identity プラットフォームでは、email や profile スコープの有無によって、ID トークンに含まれるクレームが変わることがあります。
- サインインだけなら
scope=openidでも動きますが、メールを取りたいならopenid profile emailをセットしておくのが無難です。 - MSAL などのライブラリを使う場合も、スコープ指定は自分で書くことが多いので、サンプルコードをコピペしたときに
emailが抜けていないか確認しましょう。
メール未検証+マルチテナント アプリの制限
Microsoft のガイダンスでは、検証されていないメール ドメインの email クレームは、マルチテナント アプリでは既定で削除する挙動になっています。
ドメイン所有者が検証済みとみなされるのは、概ね次のようなケースです(公式ドキュメントからの要約)。
- メールのドメインがユーザー テナントに登録され、管理者が DNS などで所有を検証している。
- Microsoft アカウント(Outlook.com など)や、Google アカウントなど一部の IdP から来たメール。
- 外部 ID の メール OTP で認証に使ったメール アドレス。
一方、「メール+パスワード」ローカルアカウントの場合は、ドメイン所有者が検証されておらず、マルチテナント構成では email クレームがそもそも入らない、というパターンが起こりえます(コミュニティ報告と公式 Q&A からの推測)。
どうしても ID トークンに email を入れたい場合、Graph API でアプリの authenticationBehaviors.removeUnverifiedEmailClaim を false に変更するというオプションもあります。ただし、これはセキュリティ上のリスクを理解した上で慎重に検討すべき設定です(ドキュメントではむしろ true にしてリスクを減らすことを推奨)。
preferred_username を実務的に活用する
ローカル アカウントでは、preferred_username にメール相当が入るケースが多くあります。実務では、email がなくても次のようなロジックでかなりのケースをカバーできます。
var email =
User.FindFirst("email")?.Value ??
User.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value ??
User.FindFirst("preferred_username")?.Value;
「email がないから即アウト」ではなく、このように 複数の候補クレームを順に見る実装を入れておくと、IdP を差し替えたときや、将来の仕様変更にも耐性が高くなります。
.NET / ライブラリ側のクレーム・マッピング
.NET では、既定で一部のクレーム名が ClaimTypes.Email などにマップされますが、マッピング設定を変更していると期待どおり取れないことがあります。
- ASP.NET Core の
JwtSecurityTokenHandler.DefaultMapInboundClaimsをfalseにしているかどうか。 - 独自の
ClaimActionsを追加してemailを別名に変えていないか。
トークンには email が見えるのに User.Claims に現れない場合は、まずフレームワーク側のマッピングを疑ってみましょう。
アプリ登録の「省略可能クレーム(Optional Claims)」や Attributes & Claims
特に SAML や一部の OIDC 連携では、アプリ側の「Attributes & Claims」設定でどのディレクトリ属性をどのクレームに出すかを制御します。External ID 向けのドキュメントでも、次のような手順が案内されています。
Entra ID > アプリ登録からアプリを開く。- [マネージド アプリケーション] のリンクを辿り、該当の「エンタープライズ アプリケーション」を開く。
- [シングル サインオン] > [Attributes & Claims] を編集する。
- メール アドレス に相当するディレクトリ属性(
mailやuserPrincipalName、signInNames.emailAddressなど)をEmailクレームにマッピングする。
OIDC / OAuth2 で 省略可能クレーム(Optional Claims) を使っている場合は、アプリ登録の [トークンの構成] で email を ID トークン/アクセス トークンに追加することもできます。
B2C カスタムポリシーで signInNames.emailAddress → email をマップする
次は、旧 Azure AD B2C テナント(Entra External ID の一部)を使っている場合です。ここでは引き続き カスタムポリシー(IEF XML) が利用できるため、従来どおり signInNames.emailAddress を email にマップすることができます。
典型的な OutputClaims の例
カスタムポリシー内の RelyingParty セクション(もしくは該当の Technical Profile)の <OutputClaims> を次のように変更します。
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email"
PartnerClaimType="signInNames.emailAddress" />
<!-- 他のクレーム定義 -->
</OutputClaims>
これにより、ディレクトリに保存されている signInNames.emailAddress 属性が、トークンの email クレームとして出力されます。
CIAM(外部テナント)ではカスタムポリシーは使えない点に注意
前述のとおり、新しい External ID for customers(CIAM テナント)ではカスタムポリシーはサポートされていません。メール ログイン専用で、ユーザーフローとトークン発行イベント(Custom authentication extensions)が主な拡張ポイントです。
そのため、
- B2C テナントを継続利用している場合 → ここで紹介した カスタムポリシーによるマッピング が有効。
- 新規の CIAM テナントのみを使っている場合 → ユーザーフロー+トークン発行イベント+Graph API での拡張を検討(次章)。
代替策・ワークアラウンド:email クレームに頼りすぎない設計
どうしても email クレームが出ない/セキュリティ要件的に email をトークンに入れたくない、という場合に有効なパターンをまとめます。
Graph API の /me からメール情報を取る
もっとも汎用的なのが Microsoft Graph API の GET /me です。
- 委任権限として
User.Read(もしくはそれを含むスコープ)をアプリに付与する。 - サインイン後、アクセス トークンを使って
GET https://graph.microsoft.com/v1.0/meを呼び出す。 - レスポンスの
mail、userPrincipalName、identitiesコレクションなどからメール相当を判断する。
この方法なら、ID トークンに email が含まれていなくても、アプリ側でユーザープロファイル情報としてメールを取得して保存することができます。ただし、「そのメールが本当に本人のものか?」という検証はアプリ側のロジックに委ねられるため、認可や一意な識別子として使うのは避けましょう。
ユーザー情報エンドポイント(userinfo)を利用する
OIDC の標準として /userinfo エンドポイントも利用可能です。scope に openid email profile を付けておけば、userinfo 応答に email が入るケースが多くあります。
Entra External ID でも、「ID トークンは最小限、詳細情報は userinfo で取る」という設計はベストプラクティスに近い形です。
トークン発行イベント(Custom authentication extensions)でクレームを拡張
より高度なパターンとして、トークン発行イベント(token issuance start event)+カスタム認証拡張を使う方法があります。
- Azure Functions 等で REST API を作成し、Microsoft Entra からの呼び出しを受け取る。
- その中で Graph API や外部 DB を参照してメールなどの属性を取得。
- 必要なクレーム(例:
app_emailやcustomer_numberなど)をトークンに追加して返す。
この方法なら、email クレームそのものではなく、アプリ専用のクレーム名で値を埋め込めるため、セキュリティ面でも設計がしやすくなります。
「email ではなく oid/tid を主キーにする」ことの重要性
どの方法を取るにせよ、アプリでユーザーを一意に識別するキーとして email を使うのは避けるのが原則です。
- 主キー候補:
sub(subject)+iss- Entra であれば
oid(オブジェクト ID)+tid(テナント ID)
- メール アドレスは「表示用」「通知先」「2 要素認証の宛先」程度にとどめる。
これを徹底するだけでも、メール変更や IdP 変更に強いアプリ設計になります。
シナリオ別:おすすめ実装パターン
| シナリオ | おすすめ構成 | ポイント |
|---|---|---|
| 自社サービス 1 テナントのみで使う Web アプリ | CIAM ユーザーフロー+openid profile email+ID トークンの email | マルチテナントでなければ email 抑制の影響が小さい。メール検証を必須にしておく。 |
| 複数企業向け SaaS(マルチテナント) | ユーザーフロー+Graph /me でメール取得+oid/tid を主キーにする | email はあくまで表示用。認可ロジックは sub/oid ベースに。 |
| 高度にカスタマイズされた B2C テナント | カスタムポリシー+signInNames.emailAddress → email マッピング | 既存の B2C 実装を活かしつつ、徐々に External ID への移行を計画。 |
| 外部システム連携や独自属性が多い CIAM | トークン発行イベント+カスタム認証拡張でクレームを拡張 | 外部 DB から会員 ID やロールを取得し、専用クレームとしてトークンに付与。 |
最小実装チェックリスト(実務向け)
Entra External ID(特に CIAM)で「サインイン後に email クレームを返したい」という場合、最低限おさえておきたい項目をチェックリストとしてまとめます。
- [ ] ユーザーフローの アプリケーションクレーム / トークンクレーム で「メール アドレス」を有効化し、ID トークン(必要ならアクセス トークン)に含める。
- [ ] OIDC 認可リクエストの
scopeにemailを含める(例:openid profile email offline_access)。 - [ ] メール検証(OTP など)を必須化するか、既存ユーザーのメール検証状態を整理する。
- [ ] ID トークンとアクセス トークンを区別して確認し、対象トークンに email が入っているかを jwt.ms で検証する。
- [ ] クライアント側で
"email"、ClaimTypes.Email、"preferred_username"など複数の候補クレームを確実に参照する。 - [ ] マルチテナント構成の場合は、メール ドメインが検証されているか、
authenticationBehaviors.removeUnverifiedEmailClaimの設定を確認(変更する場合はリスクを十分に理解した上で)。 - [ ] どうしても email が取れない/取りたくない場合は、Graph
/meや/userinfo、トークン発行イベントを使って 別名クレームとして補完する。 - [ ] 認可やユーザー一意識別には email ではなく
sub/oid+tidを使う設計にする。
まとめ:まずはユーザーフロー+email スコープ、それでもダメなら段階的に深掘り
Entra External ID で「サインイン後に email クレームを返す」には、次のようなステップで切り分けていくのが現実的です。
- 第一段階:ユーザーフローのクレーム設定で「メール アドレス」を有効化し、
scope=openid profile emailを付けて ID トークンを確認する。 - 第二段階:メール検証の有無、マルチテナントかどうか、
preferred_usernameや Optional Claims/Attributes & Claims の設定、クライアント側のクレーム マッピングを見直す。 - 第三段階:それでも email が取れない/取りたくない場合は、Graph
/meや/userinfo、トークン発行イベントを使って、アプリ専用のクレームとしてメール相当情報を補完する。 - 全段階共通:email はあくまで「表示・連絡用」であり、認可・一意識別のキーには使わない、という設計ポリシーを守る。
Azure AD B2C 時代に signInNames.emailAddress をカスタムポリシーで email にコピーしていたワークロードでも、Entra External ID では「どのテナント種別か」「マルチテナントか」「どのトークンを使うか」によって最適解が変わります。本記事のチェックリストをなぞりながら、自分のシナリオに一番フィットするパターンを選んでみてください。

コメント