Azure AD B2Cへ移行したいのに、B2Bで招待した外部Azure AD(ExternalAzureAD)ユーザーをMicrosoft Graph APIで作成すると「パスワード必須」で詰まる——。再サインアップなしでサインイン体験も変えずに移行する設計を、実装の勘所までまとめます。
よくある状況:B2Bの外部フェデレーションユーザーをB2Cへ「そのまま」持っていきたい
Microsoft Entra ID(旧Azure AD)のB2Bコラボレーションで、パートナー企業のアカウント(外部Azure AD)やGoogleアカウントを招待し、アプリにアクセスさせているケースは珍しくありません。ところが、アプリが成長して「一般ユーザー向けのサインアップ/サインイン」「ブランドUI」「多様なIDプロバイダー」「ユーザーフローの細かな制御」などが必要になると、Azure AD B2C(Microsoft Entraの顧客向けID管理機能)へ移行したくなります。
このとき現場でぶつかりやすいのが、外部Azure ADのフェデレーション専用ユーザー(B2Bゲスト)を、B2C側へ“再登録なし”で移したいという要件です。特に、B2B側でユーザーの issuer が ExternalAzureAD として表現されるパターンは、Graph APIでの事前作成がうまくいかず、設計変更を迫られがちです。
まず押さえるべき要件と制約(設計を間違えると炎上しやすいポイント)
- ユーザーに再サインアップ(再登録)させない:登録フォームの再入力や、別のアカウント作成を要求しない。
- サインイン方法を変えない:これまで「職場/学校アカウント(外部Azure AD)」で入れていたなら、同じ感覚で入れる。
- B2C側にローカルパスワードを持たせない:パスワード管理をB2Cへ移したくない(=フェデレーションのまま運用したい)。
- 可能ならAPIで一括移行したい:Microsoft Graph APIでユーザーを作成して、移行コストを下げたい。
| 要件 | ありがちな誤解 | 現実の落とし穴 |
|---|---|---|
| 再登録なし | ユーザーを事前に作ればOK | ExternalAzureADは識別子が揃わず事前作成が失敗しやすい |
| サインイン体験を維持 | UIだけ同じにすればOK | 発行者(issuer)やクレームが変わり、アプリ側の紐付けが崩れることがある |
| ローカルパスワード不要 | パスワードは空で作れるはず | Graphはローカル作成ならパスワード必須になりやすい(パスワードなしは例外的) |
Graph APIで事前作成が詰まる理由:B2Cのユーザー作成は「本人を一意に識別できる証拠」が要る
Azure AD B2CでGraph APIからユーザーを作成する場合、ざっくり言うと次のどちらかが求められます。
| 作成パターン | 必要な情報 | ユーザーのサインイン | 向いているケース |
|---|---|---|---|
| ローカルアカウント(メール+パスワードなど) | passwordProfile(パスワード)など | B2Cで認証 | 一般的なB2Cのサインアップ |
| フェデレーション専用アカウント | identities に有効な issuer と issuerAssignedId | 外部IdPへ委任 | 企業テナント/ソーシャルIDでのサインイン |
たとえば、Googleや一般的なEntra IDフェデレーションの場合、issuer(発行者)と issuerAssignedId(その発行者内の一意ID)がはっきりしているため、以下のように「フェデレーションのみ」のユーザーを作れます。
POST https://graph.microsoft.com/beta/users
{
"displayName": "DemoExternalB2CUser",
"identities": [
{
"signInType": "federated",
"issuer": "https://login.microsoftonline.com/<federated_AzureAD_tenantID>/v2.0",
"issuerAssignedId": "<federated_AzureAD_User_ObjID>"
}
]
}
しかし、B2Bの世界で登場するissuer: ExternalAzureADのユーザーは曲者です。環境や招待の仕方によって、Graph上で issuerAssignedId が欠落(null)したり、B2C側で「フェデレーションだけでは識別が不十分」と判定されてしまい、結果として次のエラーに繋がります。
A password must be specified to create a new user
ExternalAzureADの落とし穴:B2Bゲストの“表現”と、B2Cが求める“識別子”が噛み合わない
B2B(外部コラボレーション)では、招待された外部ユーザーが「ゲスト」として既存テナント内に作られます。ここで、サインインの起点が外部Azure ADである場合、Graphのユーザー情報の一部が ExternalAzureAD のような“抽象的なissuer”で表現されることがあります。
問題は、B2Cでフェデレーションユーザーを事前作成するには、「どのIdPの、誰なのか」をB2Cが検証可能な形で渡す必要がある点です。ところが issuerAssignedId が取れない/安定しないと、B2Cはそのユーザーを一意に紐付けられず、「ならローカルアカウントとして作る前提でパスワードを出して」と要求する動きになります。
重要:「一時的にランダムパスワードを付けてローカルアカウントとして作る」回避策は技術的には可能でも、“ローカルパスワードを持たせない”“サインイン方法を変えない”要件と衝突しやすく、後から運用負債になりがちです。
移行前の棚卸し:B2B側で「ExternalAzureADがどれくらい居るか」を把握する
設計を決める前に、まずはB2B側テナントでどのユーザーがExternalAzureAD相当なのかを把握すると、移行計画が立てやすくなります。Graph APIでユーザーを取得し、identities やサインイン関連情報を見て分類します。
GET https://graph.microsoft.com/v1.0/users?$select=id,displayName,userType,identities,mail,userPrincipalName
| 見たいポイント | 例 | 意味 |
|---|---|---|
userType | Guest / Member | B2B招待ユーザーはGuestになりやすい |
identities | issuer, issuerAssignedId | 外部IdPと一意に紐付けられるかのヒント |
mail / userPrincipalName | メールやUPN | 紐付け候補にはなるが、主キーとしては弱い |
ここで issuer が ExternalAzureAD だったり、issuerAssignedId が欠落しているユーザーが多い場合、事前一括作成の路線は失敗しやすいと判断できます。
推奨される解決策:JITプロビジョニング(Just-In-Time作成)へ発想を切り替える
結論から言うと、ExternalAzureADユーザーを「事前に移行(作成)」しようとする設計を捨て、「初回サインイン時にB2Cが自動作成する」JIT(Just-In-Time)プロビジョニングに寄せるのが現実解です。
| 観点 | 一括事前作成(Graph) | JITプロビジョニング |
|---|---|---|
| ExternalAzureADへの適合 | 識別子不足で失敗しやすい | 実際の認証結果から確実にリンクできる |
| ユーザー体験 | 成功すれば変わらないが、失敗時の救済が難しい | 「いつも通りサインイン」だけで裏側が整う |
| 移行コスト | 移行スクリプト/例外処理が重い | 初回サインイン時の連携実装に集中できる |
| 運用 | 移行対象の棚卸しが必要 | 利用者から順次作成されるため段階移行に強い |
JITプロビジョニングの実装イメージ(B2B→B2C移行でハマりにくい手順)
B2Cテナントに「元のMicrosoft Entra ID」を外部IDプロバイダーとして登録する
B2C側で、元のEntra IDテナント(あるいは特定の組織テナント)を外部IDプロバイダー(OIDC)として登録します。ここを正しく作ると、B2Cのサインイン画面に「職場または学校アカウントでサインイン」相当の選択肢を出せます。
- 対象を特定テナントに限定できるなら、マルチテナントより事故が少ない
- クライアントID/シークレット、リダイレクトURI、発行者(メタデータURL)の整合を取る
- 必要なクレーム(メール、表示名、
oid/tid等)が取れる設定にする
ユーザーフロー(またはカスタムポリシー)で、そのIDプロバイダーをサインイン手段として有効化する
B2Cのユーザーフローを使う場合は、対象フローの「IDプロバイダー」設定で有効化します。より細かい制御が必要ならカスタムポリシーで技術プロファイルを定義し、クレームのマッピングや条件分岐を組み込みます。
サインインUIを「従来に近い言葉」に寄せて心理的コストを下げる
ユーザー体験を変えたくない場合、ボタン表示や文言が地味に効きます。B2CのカスタムUI(ページテンプレート/ローカライズ)を使い、「職場/学校アカウントでサインイン」や社内で通っていた呼称に寄せると、問い合わせが減りやすいです。
初回サインイン時にB2Cがユーザーを自動作成し、以降はB2Cユーザーとして管理できる
外部IdPで認証が成功した瞬間に、B2C側へユーザーが自動作成されます。以降、ユーザーはB2Cテナント内に存在しますが、認証自体は外部IdPへ委任され続けます(=フェデレーションのまま)。
| タイミング | ユーザーがやること | B2C側で起きること | アプリ側でやると良いこと |
|---|---|---|---|
| 初回サインイン | 外部Entra IDでログイン | JITでB2Cユーザー作成、外部IDとリンク | 既存DBのユーザーと紐付け、属性同期、初回フラグ付与 |
| 2回目以降 | 同じ操作でログイン | 既存リンクを使って認証/トークン発行 | 通常ログイン処理 |
JITで作られたユーザーをGraphで確認する(移行が進んでいるか可視化できる)
JIT方式は「ログインした人から順に作られる」ため、移行進捗を可視化するには、B2C側のユーザー数や identities を定期的に集計すると便利です。たとえば、B2C側でユーザーを取得して identities を見ると、フェデレーションのリンク情報が格納されているはずです。
GET https://graph.microsoft.com/v1.0/users?$select=id,displayName,identities,createdDateTime
ユーザー検索をしたい場合は、identities をキーにする設計が強いです(メールは補助)。環境によってフィルターの書き方が異なるため、まずは少数で取得してパターンを掴み、運用スクリプトへ落とし込むのがおすすめです。
「移行の要」はアプリ側のJIT連携:B2Cユーザーと既存データをどう繋ぐか
ユーザーをB2Cへ“自動作成”できても、アプリ側に既存のユーザーDBや権限テーブルがある場合は、初回サインイン時にリンクする処理が必要です。ここで重要なのは、メールアドレスだけで紐付けないことです(メール変更、同一メールの別ID、重複などで破綻しやすい)。
おすすめの紐付けキー設計(壊れにくい順)
| 候補キー | 安定性 | 使いどころ | 注意点 |
|---|---|---|---|
外部Entra IDの tid + oid | 高い | 同一テナントに限定した企業ID | テナント跨ぎやアカウント統合には工夫が必要 |
B2Cの sub(ユーザー識別子) | 高い | アプリ内の主キーとして保持 | 外部IdP側のユーザー統合には直接使いにくい |
| メールアドレス | 低い | 補助キー、検索キー | 変更/重複/未提供(IdP設定次第)で破綻 |
実装としては、初回サインインで受け取るトークン(IDトークン/アクセストークン)から tid・oid・sub などを取り出し、既存DBへ紐付けを保存します。疑似コードのイメージは以下です。
// 例:サインイン後のコールバックで
claims = getClaimsFromIdToken()
// なるべく安定した識別子を組み立てる
externalKey = claims["tid"] + ":" + claims["oid"] // 取得できる場合
b2cSub = claims["sub"]
user = db.findUserByExternalKey(externalKey)
if user is null:
// 既存B2BのユーザーDBがあるなら、メール等で“候補”を引いて本人確認を追加する選択肢も
user = db.createUser(
primaryKey = b2cSub,
externalKey = externalKey,
email = claims.get("email"),
displayName = claims.get("name"),
migratedAt = now()
)
else:
// すでに紐付いているなら通常ログイン
db.updateLastLogin(user.id, now())
段階移行を成功させる運用設計:B2BとB2Cを“同時に走らせる”期間を作る
実務では、ある日いきなり全ユーザーをB2Cへ切り替えるより、段階移行が安全です。JITプロビジョニングは「ログインした人から順次移行される」ため、段階移行と相性が良いです。
| フェーズ | やること | ユーザー影響 | チェックポイント |
|---|---|---|---|
| 準備 | B2Cテナント/IdP/ユーザーフロー構築、アプリの認証切替 | なし | テストユーザーでJIT作成、クレーム/属性を確認 |
| 並走 | 一部ユーザー/環境でB2C導入、ログイン時に自動移行 | 限定的 | サインイン成功率、サポート問い合わせ、ログを監視 |
| 切替 | 本番の認証入口をB2Cへ統一、旧B2Bは参照用に残す | 中 | 未移行ユーザーの導線、FAQ、連絡手段を整備 |
| 収束 | 移行率が十分になったら旧B2B依存を縮小 | 低 | 退会/監査/ログ保全、バックアップ |
「どうしても事前作成したい」場合に検討できる代替案(ただし要件とトレードオフ)
要件が厳しいほど、代替案は“どこかを妥協する”形になります。代表的な選択肢を整理します。
| 代替案 | できること | 主なリスク/デメリット | 向く条件 |
|---|---|---|---|
| ローカルアカウントとして作成(ランダムパスワード) | Graphで一括作成は可能になりやすい | ローカルパスワード運用が発生し、要件と衝突 | UXを多少変えてもよい/将来ローカル移行したい |
| 最初の1回だけJITで作らせ、その後に属性を一括補完 | “移行済み”に近い状態へ整える | 全員が初回ログインするまで完全移行にならない | 段階移行を許容できる |
| カスタムポリシーで外部API(REST)連携し、サインイン時に既存DBと照合 | 初回リンクの厳密さを上げられる | ポリシーが複雑化し、保守コストが上がる | 本人確認/属性同期が厳格に必要 |
トラブルシューティング:JIT移行でよくある詰まりどころ
| 症状 | よくある原因 | 確認/対処のヒント |
|---|---|---|
| サインイン画面に外部Entra IDのボタンが出ない | ユーザーフローでIdPが有効化されていない | 対象フローのIDプロバイダー設定、ポリシー参照を再確認 |
| 外部認証は成功するがB2C側にユーザーが作られない | 戻りクレーム不足、ポリシーのクレームマッピング不整合 | 最低限の識別子クレーム(sub/oid等)を受け取れているか確認 |
| 既存DBとの紐付けがズレる/重複ユーザーが増える | メールだけで紐付けしている | tid+oid等の安定キーへ切替、初回のみ本人確認ステップを挟む |
| テナント外のユーザーまでログインできてしまう | マルチテナント設定/許可範囲が広い | 対象テナントを限定、必要なら許可ドメインの制御を入れる |
| 「ユーザーは作られたが属性が空」になって困る | IdP側でクレームが出ていない/スコープ不足 | 必要クレーム(name/email等)の取得方法、同意(consent)やスコープを点検 |
セキュリティと監査の観点:B2Cに移しても“統制”を落とさないために
- 外部Entra ID側でのMFA/条件付きアクセス:認証を委任する以上、元テナント側のポリシーが実質の防波堤になります。
- ログの見え方が変わる:B2C側と外部IdP側のログを突き合わせられるよう、ユーザー識別子の設計(tid/oid/sub)を揃えます。
- アカウントライフサイクル:退職/無効化は外部IdP側で完結するのか、B2C側のユーザーも無効化するのか、運用ルールを明文化します。
- アプリの認可設計:トークンのクレーム(グループ/ロール/スコープ)をどう使うかを再確認し、B2C移行後も同等の権限判定ができるようにします。
まとめ:ExternalAzureADを「一括移行」しようとせず、JITで“自然に移行させる”のが最短ルート
- ExternalAzureADのユーザーは、Graph APIで事前作成しようとすると識別子不足で「パスワード必須」エラーになりやすい。
- UX要件(再登録なし・サインイン方法維持・ローカルパスワード不要)を守るなら、JITプロビジョニングへ設計転換するのが現実的。
- 成功の鍵は、初回サインイン時にアプリ側で既存DBと正しく紐付けること。メール依存は避け、安定した識別子で設計する。
「移行」は一括バッチだけが正解ではありません。ExternalAzureADのように“事前作成が難しい”領域では、サインインをトリガーにした段階的移行が、ユーザー体験と実装現実の両方を守る近道になります。

コメント