ExternalAzureADのB2BユーザーをAzure AD B2Cへ移行:Graph APIが通らない理由とJITプロビジョニング手順

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でユーザーを作成して、移行コストを下げたい。
要件ありがちな誤解現実の落とし穴
再登録なしユーザーを事前に作ればOKExternalAzureADは識別子が揃わず事前作成が失敗しやすい
サインイン体験を維持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
見たいポイント例意味
userTypeGuest / MemberB2B招待ユーザーはGuestになりやすい
identitiesissuer, 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のように“事前作成が難しい”領域では、サインインをトリガーにした段階的移行が、ユーザー体験と実装現実の両方を守る近道になります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次