Entra ID(通常のAzure AD)のトークンエンドポイントではPostmanからトークンを取得できるのに、Azure AD B2C(Microsoft Entra External ID)の「ポリシー付きトークンエンドポイント」に切り替えると失敗する――この差は、URL形式やスコープだけでなく、アプリ登録の設定が原因になりがちです。本記事では切り分け手順と、最終的に解決した「サポートされるアカウントの種類」の落とし穴を、再現しやすい形で整理します。
現象:Entra ID では成功するのに Azure AD B2C では失敗する
よくある相談として、「login.microsoftonline.com の v2.0 トークンエンドポイントでは同じパラメータで成功するのに、Azure AD B2C の {tenant}.b2clogin.com に切り替えると invalid_client や unauthorized_client、あるいは AADB2C で始まるエラーが返ってきて先に進めない」というケースがあります。
特にカスタムポリシー(XML の PolicyId を持つ TrustFramework)を使っていると、「必要なクレームは定義した」「ID プロバイダーも繋いだ」「なのにトークンが取れない」という状態になりがちです。ここで重要なのは、B2C のトークンエンドポイントは “普通の Entra ID と同じようで同じではない” こと、そして “ポリシーに合わせた URL・スコープ・アプリ登録” の三点セットで初めて成立することです。
まず押さえる:Azure AD B2C のトークン発行は「ポリシー(ユーザーフロー/カスタムポリシー)」が前提
Entra ID(通常の Azure AD)ではテナントに対して単一の認証基盤があり、/oauth2/v2.0/token は「そのディレクトリでのサインイン」を前提に動きます。一方、Azure AD B2C ではサインイン体験そのものを「ポリシー」として定義し、トークン発行もそのポリシー文脈で行われます。つまり、B2C では “どのポリシーで認証したのか” がトークン発行の前提条件になります。
| 観点 | Entra ID(通常の Azure AD) | Azure AD B2C |
|---|---|---|
| エンドポイントのホスト | login.microsoftonline.com が一般的 | {tenant}.b2clogin.com が推奨(B2C 専用) |
| 認証体験の単位 | テナント(ディレクトリ) | ポリシー(ユーザーフロー/カスタムポリシー) |
| トークン要求で意識するもの | tenant / client / scope | tenant / policy / client / scope |
| 典型的なつまずき | 同意(consent)、API スコープ、リダイレクト URI | ポリシー指定方法、スコープ記法、アプリ登録のアカウント種別 |
切り分けの最短ルート:トークン URL は「p=」形式でも試す
Azure AD B2C のトークンエンドポイントは、ポリシー指定を URL パスに埋め込む方法と、クエリパラメータ p= で指定する方法の二通りが広く使われています。環境やツールの癖(プロキシ、WAF、URL 正規化、Postman の自動補完など)でパス埋め込み版が想定外に扱われることがあるため、まずは p= 形式での疎通 を試すと原因の切り分けが速くなります。
| 形式 | 例 | ポイント |
|---|---|---|
| パスにポリシーを埋め込む | https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/<policy>/oauth2/v2.0/token | ドキュメントやサンプルで見かける。URL 正規化の影響を受けることがある。 |
p= でポリシー指定 | https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/oauth2/v2.0/token?p=<policy> | 切り分け用途でおすすめ。ポリシー部分をパラメータとして扱えるので比較が容易。 |
どちらを使っても最終的には同じポリシーを指している必要があります。カスタムポリシーの場合、B2C_1A_... のようなポリシー名が URL 上の <policy> に入ります。XML の PolicyId を変更したり、アップロード先のポリシーを取り違えたりすると、見た目は合っているのに別物を叩いている状態になるので注意してください。
スコープは「最小構成」で一度通してから広げる
B2C のトークン取得に失敗する原因の多くは、実は「ポリシー」そのものではなく、スコープ指定のミスです。特に API 用のスコープ(Expose an API で定義した範囲)を手入力していると、URI の揺れ や スラッシュ区切り、プレフィックス(api:// か https://tenant.onmicrosoft.com/... か) で詰まります。
そこでまずは動作確認として、「認証できてトークンが返る」ことだけを狙う最小スコープで検証します。例えば次のような構成です。
| 目的 | scope の例 | 期待する結果 | ここで失敗したら |
|---|---|---|---|
| まず通るか確認(ログイン+ID トークン) | openid(+必要なら offline_access) | ID トークンが返る(フローによっては access_token も返る) | URL、ポリシー、client 設定、リダイレクト URI を疑う |
| 長期セッション確認(更新用) | openid offline_access | refresh_token が返る(許可されるフローの場合) | アプリ種別(Public/Confidential)やフロー設定を疑う |
| API を呼びたい(アクセス トークンの aud を API にする) | ポータルで表示される API スコープ文字列(例:api://.../read や https://.../read) | API 宛の access_token が返る | Expose an API の設定、同意、スコープ文字列の誤りを疑う |
今回のケースのように「まずは動作確認用」として openid <application-id> offline_access のような最小構成を試すのも有効です。ここで通るなら、次に疑うべきはスコープ指定や API スコープの書き方である可能性が高い、という判断ができます。
Postman は Authorization 機能より「Body 直指定」で差分を潰す
Postman には OAuth 2.0 の取得を支援する UI(Authorization タブ)がありますが、B2C のようにポリシーや細かなパラメータ差異がある環境では、UI が内部で付与する値(scope の並び、ヘッダー、PKCE の扱いなど)がブラックボックスになり、切り分けが難しくなります。
そのため、トラブル時は Body(x-www-form-urlencoded)でトークン要求を明示的に組む のがおすすめです。何を送っているかが 100% 見えるので、URL とパラメータの差分を最短で特定できます。
Postman での基本設定(トークン要求の例)
- Method を POST にする
- URL を B2C のトークンエンドポイントにする(まずは
p=形式推奨) - Headers に
Content-Type: application/x-www-form-urlencodedを設定(Postman が自動で付けることもあります) - Body を x-www-form-urlencoded にしてキー・値を入力する
| パラメータ | 例 | 用途 | つまずきポイント |
|---|---|---|---|
grant_type | authorization_code / refresh_token など | OAuth フローの指定 | フローに応じた必須パラメータが変わる |
client_id | xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | アプリ(クライアント)の識別 | “B2C テナント側” のアプリ登録を使っているか確認 |
client_secret | ******** | Confidential client の証明 | Public client なのに付ける/逆に必要なのに付けない |
code | 0.AAA... (authorization code) | 認可コード(authorization_code フロー) | リダイレクト URI とセット。期限切れ・二重使用でも失敗 |
redirect_uri | https://oauth.pstmn.io/v1/callback など | 認可コードの戻り先 | Authorize リクエストと完全一致が必要(末尾スラッシュ含む) |
scope | openid offline_access(+ API scope) | 発行してほしい権限 | スコープ文字列の誤りが最多。まず最小構成で通す |
例えば、認可コードをすでに取得できている前提なら、token 交換は次のような形になります。
POST https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/oauth2/v2.0/token?p=<policy>
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&client_id=
&client_secret=
&code=
&redirect_uri=
&scope=openid%20offline_access%20
「UI で取れないけど Body 直だと取れる」「逆に Body 直でも取れない」など、結果の違い自体が重要なヒントになります。UI 側は便利ですが、切り分けには向きません。
最終的な原因:アプリ登録の「サポートされるアカウントの種類」が B2C に不適切だった
ここまで URL とスコープ、Postman の送信内容を揃えても通らない場合、見落とされがちなのが App registration(アプリ登録)の基本設定です。今回の採用回答として決定打になったのは、アプリ登録の 「サポートされるアカウントの種類」 が B2C のユーザーフロー/ポリシーで認証する前提と噛み合っていなかったことでした。
| 設定項目 | NG 例 | OK 例 | 影響 |
|---|---|---|---|
| サポートされるアカウントの種類 | シングルテナント(例:「この組織ディレクトリ内のアカウントのみ」) | ユーザーフローで認証できる種類(例:「任意の ID プロバイダーまたは任意の組織ディレクトリのアカウント」など) | B2C のポリシー文脈での認証・トークン発行が成立せず、どれだけポリシーを直しても解消しない |
この設定を適切なものに変更(または作り直し)したところ、B2C 側でもアクセストークンを正常に取得 できるようになりました。
なぜアカウント種別が原因になるのか
Azure AD B2C は、企業アカウントだけでなく、ローカルアカウントやソーシャル ID、外部テナントなど多様な ID を “ポリシー” で束ねて扱います。一方で、アプリ登録が「この組織内だけ」という前提(シングルテナント)だと、B2C の想定する認証ルートと合わず、結果としてトークン要求が弾かれます。
ここが厄介なのは、ポリシー側のクレーム定義や Technical Profile をいくら調整しても、そもそものアプリ登録の前提が違うと通らない ことです。つまり「トークンエンドポイントを叩く権利(前提条件)」が満たされていない状態なので、エンドポイントやクレームの修正では直りません。
修正の手順(チェックポイント付き)
- Azure portal で B2C テナントに切り替える(ディレクトリ切替の取り違えが多いので注意)
- 対象アプリの App registration を開き、Supported account types(サポートされるアカウントの種類) を確認する
- B2C のユーザーフロー/カスタムポリシーで認証する用途に合う選択肢へ変更する(変更できない・影響が大きい場合は新規作成して切り替える)
- 必要に応じて Redirect URI、Client secret、API スコープ(Expose an API)を再設定する
- Postman で再度「最小スコープ+p=形式 URL」でトークン取得を試す
実務上は、この設定を変えたら client_id が変わる ことが多い(作り直す場合は確実に変わる)ため、Postman の環境変数やアプリ側の設定も一緒に更新しないと「直したのに直ってない」状態になります。URL やスコープと同じくらい、クライアント側の参照先も確認してください。
追加のチェックリスト:ここを押さえると再発しにくい
原因がアカウント種別だったとしても、同じ現象に見える別要因は多くあります。最後に、B2C の「ポリシー付きトークンエンドポイント」で Postman からトークン取得できないときに、順番に潰していけるチェックリストをまとめます。
| チェック項目 | 確認する場所 | よくあるミス | 対処のヒント |
|---|---|---|---|
| トークン URL の形式 | Postman の URL 欄 | テナント名やドメイン(.onmicrosoft.com)の抜け、ポリシー名違い | まずは p=<policy> 形式で固定して比較する |
| ポリシー名の一致 | B2C のユーザーフロー一覧/カスタムポリシー一覧 | 似た名前のポリシーを叩いている、アップロード先を間違えた | 実際にブラウザで authorize を通し、同じポリシーで token 交換する |
| スコープの文字列 | Expose an API(スコープ表示) | 手入力で 1 文字違い、URI の前半が違う | ポータルの表示をコピーし、最小スコープで通した後に追加する |
| リダイレクト URI | App registration の Redirect URIs | authorize と token で URI が微妙に違う(末尾スラッシュ、http/https) | Postman のコールバック URL を登録し、完全一致を徹底する |
| クライアント種別 | Public client / Confidential client の扱い | secret が必要なのに未設定、または不要なのに送っている | フロー(PKCE か、サーバー側交換か)に合わせて整理する |
| アプリ登録のアカウント種別 | Supported account types | シングルテナントのまま B2C ユーザーフロー用途に流用 | B2C 用途に合う選択肢へ変更/作り直し |
実務の勘所:最短で解決する進め方
現場で最短ルートを取りたいなら、次の順番がおすすめです。ポイントは「変数を減らし、まず通る形を作ってから広げる」ことです。
- URL を固定:まずは
https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/oauth2/v2.0/token?p=<policy>に統一 - スコープを最小化:
openid(必要に応じてoffline_access)で一度成功させる - Postman は Body 直指定:UI の自動挙動を排除して送信内容を完全に可視化
- アプリ登録の土台を確認:Supported account types、Redirect URI、Client secret を先に揃える
- 最後に API スコープを追加:ポータルからコピーして 1 つずつ増やし、どこで崩れるかを見る
「ポリシー側にクレームは定義しているはず」というときほど、ポリシーの中身よりも アプリ登録と URL・スコープの整合 のほうがボトルネックになっていることが多いです。B2C のトラブルシュートは、ポリシーを疑う前に “入口” を正しく整えるのが勝ち筋です。
まとめ
Azure AD B2C の「ポリシー付きトークンエンドポイント」で Postman からトークン取得できない問題は、URL 形式やスコープ、Postman の設定差分に目が行きがちですが、根本原因が App registration の「サポートされるアカウントの種類」 というケースがあります。
まずは p= 形式のトークン URL と 最小スコープ と Body 直指定 で切り分けを行い、それでも通らないときは アプリ登録のアカウント種別 を最優先で確認してください。ここがズレていると、エンドポイントやクレーム定義をいじっても解消しません。正しい前提を揃えれば、B2C 側でも安定してトークンを取得できるようになります。

コメント