Microsoft Entra ID(Azure AD)OIDCでid_tokenが返らない原因と対処法|response_type・acceptMappedClaims(AADSTS50146)

Microsoft Entra ID(旧 Azure AD)の OIDC(OpenID Connect)認証で、「認可コードは取れるのに id_token が返らない」「結果としてアプリ側の JWT デコードが The token is null で落ちる」という事象は、設定とリクエストの噛み合わせで起きがちです。この記事では、実際に解消した手順(response_type と acceptMappedClaims)を軸に、再発しないための確認ポイントまで整理します。

目次

現象:/authorize ではコードが取れるが、/token で id_token が返ってこない

今回の状況を、発生事象→影響の形で整理すると以下です。

フェーズ期待実際影響
/authorize認可コード(authorization code)取得認可コードは取得できる一見フローは正常に見える
/tokenaccess_token と(必要なら)id_token の取得access_token は返るが id_token が null / 欠落ログイン成立の判断材料が欠ける
アプリ側処理id_token を JWT として検証・デコードcom.auth0.jwt.exceptions.JWTDecodeException: The token is null認証処理が例外で停止

ポイントは「アクセストークンが取れている=ログイン成功」ではない点です。アクセストークンは “API を呼ぶための鍵” であり、ログイン(誰としてサインインしたか)の証明は基本的に id_token が担います。

まず押さえる:access_token と id_token の役割の違い

id_token が返らない問題を早く解くには、2種類のトークンの用途を切り分けるのが最短です。

項目access_tokenid_token
目的API(例:Microsoft Graph / 自社API)にアクセスする「このユーザーでサインインした」をアプリが確認する
主な受け手API サーバークライアント / アプリ(サインイン処理)
中身の例権限(scp / roles)、対象リソース、期限などユーザー識別(sub)、発行者(iss)、対象(aud)、nonce など
検証の観点API 側で署名・発行者・aud・スコープ/ロールを確認アプリ側で署名・発行者・aud・nonce を確認(セッション確立)
「返ってくる/こない」問題の切り分けAPI のスコープ要求が通っていれば返りやすいOIDC として正しく要求していないと返らない/ブロックされる

つまり、id_token がない状態で “ログイン完了” 扱いにしている実装だと、ちょっとした設定差で一気に崩れます。今回のように、JWT ライブラリが null を受け取って例外を投げるのは典型です。

Entra ID のフロー差分:id_token は「どこで」返るのか

OpenID Connect にはいくつかのバリエーションがあり、id_token が返るタイミングも変わります。混乱しやすいので、まずは “どのフローを採用しているか” を明確にするのが重要です。

フロー/authorize/token特徴向いているケース
認可コードフロー(基本)codeaccess_token(+通常は id_token も返り得る)バックチャネルでトークン交換。PKCE と相性が良いWeb/ネイティブ/SPA(PKCE)
ハイブリッドフローcode + id_token(フロントチャネルで id_token も返す)access_token(+必要なら追加のトークン)認可レスポンスで id_token を先に受け取る要件上、早期に id_token が必要な構成
(参考)インプリシットフローid_token / access_token(交換なし)フロントチャネルで直接トークン受領。設計上の注意が多い新規採用は慎重に

今回の解決は、サポート指摘に従って 「/authorize で id_token を明示的に要求する(ハイブリッドフロー)」に切り替えたことが第一歩でした。

切り分けの最短ルート:id_token が返らないときに最初に見るべきチェック表

“設定は見直したのに原因が分からない” 状況では、/authorize と /token のリクエスト内容が実際にどうなっているか(ログ/トレース)を起点に切り分けるのが効果的です。以下は実務で使いやすい確認表です。

チェック項目見る場所OKの目安NGだと起きがちなこと
scope に openid が含まれる/authorize のクエリscope=openid ...OIDC として扱われず id_token が発行されない
id_token をどこで受け取る設計かフロー設計(A)/token で受け取る or(B)/authorize で受け取る実装とフローがズレて “ないはずの場所” を探し続ける
response_type の指定/authorize のクエリ今回の解決では code id_tokenid_token が返らず、アプリ側が null を処理できず落ちる
nonce の有無/authorize のクエリnonce を付与し、検証するid_token の検証が通らない/リプレイ対策が弱い
redirect_uri の一致/authorize と /token完全一致(末尾スラッシュ等も含む)トークン交換で失敗、または想定外のレスポンスになる
PKCE の整合/authorize と /tokencode_challenge と code_verifier が対応コード交換に失敗、再試行地獄に陥る
アプリ登録の「ID トークン」許可Azure ポータルの認証設定ハイブリッド/インプリシット関連の許可が必要な構成か確認/authorize で id_token を要求してもブロックされる

今回のケースでは、scope 自体は揃っていても、“どの応答で id_token を返してほしいのか” が明確に伝わっていない状態になっていたのが大きなポイントでした。

解決策:/authorize で id_token を明示的に要求する(ハイブリッドフロー)

サポートからの指摘に沿って、/authorize の response_type に id_token を含め、「コードに加えて ID トークンも返す」形(ハイブリッドフロー)に切り替えます。

/authorize リクエスト例(イメージ)

以下は “形” を掴むための例です。値は自環境のものに置き換えてください。

GET https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/authorize
  ?client_id={client_id}
  &response_type=code%20id_token
  &redirect_uri={redirect_uri}
  &response_mode=form_post
  &scope=openid%20profile%20email
  &state={random_state}
  &nonce={random_nonce}
  &code_challenge={S256_code_challenge}
  &code_challenge_method=S256

ここでの実務ポイントは次のとおりです。

  • response_type=code id_token により、認可レスポンスに id_token を含める意図を明示できます。
  • nonce は id_token を受け取る場合の要。付与するだけでなく、アプリ側で検証する前提にします。
  • response_mode=form_post にしておくと、トークンが長いケースでも取り回しが安定します(クエリ長制限の回避)。
  • SPA/ネイティブでも、可能なら PKCE(code_challenge / code_verifier)を採用しておくと安全です。

何が変わるのか:レスポンスのイメージ

ハイブリッドにすると、リダイレクト(または form_post)で返るパラメータに id_token が含まれるようになります。

パラメータ役割この問題との関係
code/token でアクセストークン等に交換するためのコード従来通り取得できていた
id_tokenユーザーのサインインを表す ID トークンここが null/欠落していたため、JWT デコードで例外が出ていた
stateCSRF 対策の相関値必ず検証し、セッションに紐づける

これにより、少なくとも「アプリがログイン判定に使う id_token」を確実に受け取れる構成へ寄せられます。

追加でハマるポイント:id_token 要求後に AADSTS50146 が出る

id_token を要求できるようにした途端、次の壁として AADSTS50146 が出ることがあります。今回のケースでは、原因がアプリ登録側のクレーム構成にありました。

具体的には、アプリ登録でクレームマッピング(カスタムクレーム)を使っている/使う設定になっている場合、マニフェストの acceptMappedClaims が false のままだと、ID トークンの発行がブロックされることがあります。

解決策:アプリ登録マニフェストで acceptMappedClaims を true にする

手順は次のとおりです(ポータルの文言は表示時期により多少変わります)。

  1. Azure ポータル → 「アプリ登録」 → 対象アプリを開く
  2. 左メニューの「マニフェスト」を開く
  3. JSON 内の acceptMappedClaims を探し、次のように変更する
"acceptMappedClaims": true
  1. 保存して反映させる

この変更後、認可コードおよび ID トークンが正常に取得できる状態になります(今回の事例)。

いつ acceptMappedClaims が効いてくるのか(整理)

「何でもかんでも true にする」のは避けたいので、使いどころをざっくり分けます。

やりたいこと代表的な手段acceptMappedClaims が絡みやすい補足
標準クレームを少し追加/削除したいトークン構成(Optional Claims)低いUI で完結することが多い
特殊な条件でクレーム値をマッピングしたいクレームマッピング(カスタム)高いマニフェスト/ポリシー設定が絡む
アプリ側の認可(ロール)をトークンに入れたいApp roles / グループクレーム等中発行クレーム量や設計に注意

今回のように “id_token を出したい” という目的でも、背後に「クレームをどう扱うか」の設定が入っていると、結果的にトークン発行が止まります。エラーコードが出たら、まずはクレーム周り(マニフェスト/トークン構成/ポリシー)を疑うのが近道です。

実務の確認手順:変更後にどこまでテストすべきか

設定を変えたら、次の3点をセットで確認すると「直ったつもり」を避けられます。

/authorize の戻りで code と id_token を確認

  • code が取れているか
  • id_token が null ではなく、JWT 形式(xxxxx.yyyyy.zzzzz)になっているか
  • state が送った値と一致しているか

/token のレスポンスを確認(必要に応じて)

/token 側でも id_token を利用する設計なら、レスポンス JSON のキーを目視で確認します。トークン交換自体の例(イメージ)は以下です。

POST https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id={client_id}
&client_secret={client_secret}         (機密クライアントの場合)
&code={authorization_code}
&redirect_uri={redirect_uri}
&code_verifier={code_verifier}         (PKCE の場合)

そしてアプリ側ログで、少なくとも次を確認します。

  • id_token が “存在する” こと(null でない)
  • 署名検証が通ること(鍵/JWKS の取得やキャッシュ含む)
  • 検証すべきクレームが想定通りであること(iss/aud/exp/nonce など)

JWT デコード例外を「防ぐ」実装も入れておく

根本原因を解消しても、運用上は一時的な失敗(ネットワーク、再試行、途中キャンセル)で id_token が取れない状況がゼロにはなりません。ライブラリに null を渡して例外で落とすより、先にガードすると障害の形が綺麗になります。

観点おすすめ避けたい
入力チェックid_token が空なら “認証失敗” として扱い、ログに残すnull をそのまま JWT デコーダに渡す
ログ相関ID(state)やエラーコードをセットで残す例外スタックだけで原因追跡する
リトライユーザー操作が必要な場合は再ログイン導線を用意無限リトライでフローを壊す

よくある勘違い:/token に scope=openid を付けたのに id_token が出ない

現場で多いのが、「/token のリクエストで scope=openid profile email を付けたから OIDC のはず」という思い込みです。実際は、認可コードが “どの要求で発行されたか” が重要で、/authorize 側の要求内容が反映されます。

今回の解決がハイブリッドフローだったのは、/authorize の段階で id_token を明示要求し、サーバー側に「この認証で ID トークンが必要」という意図を強く伝えるため、と捉えると理解しやすいです。

再発防止のチェックリスト

最後に、同種の “id_token がない” 問題を潰すための実務チェックリストをまとめます。

項目やること期待できる効果
/authorize を最優先で点検response_type と scope(特に openid)を確認OIDC としての要求不足を最短で潰せる
id_token の受け取り場所を明確化/authorize で受け取るのか /token で受け取るのかを設計に落とす「ない場所を探す」デバッグを回避
nonce/state を必ず運用生成・保存・照合を実装に組み込むセキュリティと原因追跡が両立する
クレーム周りの設定を棚卸しカスタムクレーム/マッピングがあるなら acceptMappedClaims を確認AADSTS50146 のような “発行ブロック” を避けやすい
アプリ側のガードnull/空トークン時の分岐とログを用意障害時に落ち方が綺麗になり復旧が早い

まとめ

Microsoft Entra ID(Azure AD)の OIDC 認証で id_token が返らないときは、アプリ登録の設定だけでなく、/authorize リクエストが “ID トークンを必要としている” ことを正しく伝えているかが最重要ポイントになります。今回のケースでは、次の2点で解消しました。

  • /authorize の response_type に id_token を含める(code id_token)
  • acceptMappedClaims を true にして、クレームマッピング起因の発行ブロックを解除する

同様の症状が出たら、まずは “フロー(どこで id_token を受け取る想定か)” と “クレーム設定(acceptMappedClaims を含む)” をセットで見直すと、最短距離で復旧できます。

この記事を書いた人

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

コメント

コメントする

目次