Microsoft Entra ID 経由で Google サインインを実装したのに、トークンのクレームから氏名やアイコン写真(プロフィール画像)が取れない――この現象は“設定ミス”というより、Entra が担う役割を理解すると納得できる挙動です。原因の切り分けと、確実にプロフィール情報を取得する実装パターンを整理します。
起きていること:アプリが見ているのは「Google のトークン」ではなく「Entra のトークン」
Microsoft Entra ID に Google を外部 ID プロバイダーとして追加してサインインさせる場合、アプリが最終的に受け取るのは Entra が発行したトークン(Entra ID トークン) です。ここが最重要ポイントです。
外部プロバイダー連携は、しばしば「Google でログイン=Google のプロフィールがそのまま返ってくる」と誤解されがちですが、Entra が間に入る構成では次のような“役割分担”になります。
| 登場人物 | 役割 | アプリが受け取る主な成果物 | 期待しがちな誤解 |
|---|---|---|---|
| 外部 ID プロバイダー(本人確認の元) | (Entra に対して)Google の認証結果・トークン | 「Google のクレームがアプリへ素通しされる」 | |
| Microsoft Entra ID | ブローカー/認証基盤(アプリに対する発行者) | Entra が発行したトークン | 「外部プロバイダーの情報がトークンに全部入る」 |
| あなたのアプリ | リライングパーティ(トークンの利用者) | Entra トークンのクレーム | 「name/picture が必ず入る」 |
つまり、アプリがクレームを覗いても Google の氏名・写真が見つからないのは、珍しいことではありません。Entra がアプリに対して“保証できる範囲”の属性(最小限の識別情報、構成によってはメール)に寄せて発行するためです。
症状の典型:氏名・写真が取れない/空になる/期待するクレーム名が存在しない
「プロフィール情報が取れない」と一口に言っても、現場では次のような形で現れます。
- Entra の ID トークン(JWT)に name / given_name / family_name / picture が存在しない
- 存在しても空文字、または期待と違う(例:表示名がメールアドレスのローカル部になる)
- メールアドレスは取れるが、写真 URL だけがどうしても取れない
- Google 側ではプロフィール写真を設定しているのに、アプリには来ない
- 「Google の sub(Google 固有 ID)」を期待したが、Entra 側のユーザー識別子しか出てこない
この症状は、Entra が“Google のアカウント情報をそのままアプリに渡す設計”ではなく、Entra のディレクトリ上のユーザー表現として正規化し、必要最小限のクレームでアプリに渡す設計に寄っていることが背景にあります。
まず確認すべき:あなたがデコードしている JWT の発行者(iss)はどこか
トラブルシューティングで最初にやるべきは、トークンの中身を「どこの発行者のトークンなのか」で整理することです。JWT をデコードし、iss(issuer)を確認します。
| 確認項目 | 見る場所 | 意味 | 判断の目安 |
|---|---|---|---|
| iss(issuer) | JWT クレーム | そのトークンを“発行した主体” | Entra のドメインなら「Entra トークン」。Google なら「Google トークン」 |
| aud(audience) | JWT クレーム | 想定される受け手(クライアント) | あなたのアプリ(クライアント ID)か |
| scp / roles | JWT クレーム | 権限(API 呼び出しの可否) | プロフィール取得の権限とは別概念。過信しない |
| idp / tfp など | (構成により)JWT クレーム | 外部 IDP やポリシーの痕跡 | 「Google 経由でログインした」事実確認に有用 |
もし iss が Entra 側の値であれば、あなたが現在扱っているのは Google の ID トークンではなく、Entra の ID トークンです。その時点で「Google の picture クレームが入っていない」は自然な結論になります。
結論:Entra のトークンだけで Google のプロフィール情報を“完結”させようとすると詰まりやすい
ここからが本題です。あなたが欲しいのは「Google のプロフィール情報(氏名・写真など)」であり、しかし手元にあるのは「Entra のトークン」。このギャップを埋めるには、方針を明確にする必要があります。
実務で現実的な落としどころは、大きく次の 3 パターンです。
| 解決パターン | 何をするか | 向いているケース | 注意点 |
|---|---|---|---|
| Google のトークンで Google に取りに行く | Google のアクセストークン/ID トークンを取得し、UserInfo などでプロフィール取得 | 写真が必要、Google の最新プロフィールを反映したい、将来 Google API も呼ぶ | Entra だけのログインより実装が増える(同意・スコープ・トークン管理) |
| アプリ側でプロフィールを持つ | 初回ログイン後に氏名・写真を入力/アップロードしてもらい、以後はアプリ DB を参照 | 写真は UI 用途だけ、認証方式が複数(Google/Apple/メール等)で統一したい | ユーザー入力の UX が必要。同期はしない(割り切る) |
| Entra の属性マッピングで“取れる範囲”だけ取る | displayName/email 等、Entra が扱える範囲のクレームを整える | 氏名やメールが取れればよい(写真までは不要) | 写真は難しいことが多い。取れる属性は構成・仕様で変わる |
以降では、最も“確実にプロフィール情報を得る”方向として、Google のトークンを使って Google から取得するパターンを中心に解説し、その上で「Entra を使い続けるための現実的な設計」も整理します。
最優先で押さえる:プロフィール取得の最短ルートは UserInfo エンドポイント
Google のプロフィール属性(氏名、写真 URL など)をアプリで取りたいなら、OpenID Connect の基本に立ち返るのが最短です。代表的なのが次の UserInfo エンドポイントです。
Google OpenID Connect UserInfohttps://openidconnect.googleapis.com/v1/userinfo
ここへ Google のアクセストークンを付けて呼び出すと、ユーザーの基本プロフィール(name, picture など)を JSON で取得できます。重要なのは、Entra のアクセストークンではなく Google のアクセストークンで呼ぶことです。
UserInfo で返りやすい属性の例
返ってくる項目は状況やスコープにより変動しますが、基本的には次のようなフィールドが出ることが多いです(例)。
| フィールド | 意味 | 用途例 | 注意点 |
|---|---|---|---|
| sub | Google 側の安定識別子 | アプリ内の連携キー(Google アカウントとの紐付け) | メールより安定。メール変更に強い |
| name | 表示名(フルネーム) | UI 表示 | 本人が変更できる。本人確認用途に使わない |
| given_name / family_name | 名/姓 | 宛名、フォーム初期値 | 文化圏で構造が合わないことがある |
| picture | プロフィール写真 URL | アイコン表示 | URL は変わり得る。外部 URL 参照の設計が必要 |
| メールアドレス | 連絡先、ログイン補助表示 | 一意キーにしない(変更され得る) | |
| email_verified | メール検証済みか | 登録フローの分岐 | “アプリ側での到達確認”とは別物 |
curl での呼び出し例
サーバーサイド(推奨)で、Google のアクセストークンを使って呼びます。
curl -H "Authorization: Bearer GOOGLE_ACCESS_TOKEN" \
https://openidconnect.googleapis.com/v1/userinfo
レスポンス例(概形):
{
"sub": "109876543210123456789",
"name": "Taro Yamada",
"given_name": "Taro",
"family_name": "Yamada",
"picture": "https://lh3.googleusercontent.com/a/....",
"email": "[email protected]",
"email_verified": true,
"locale": "ja"
}
Entra のトークンに picture が入らない問題は、UserInfo を使うと「必要な情報を、必要な発行者(Google)から直接取る」構造になるため、根本的に解消しやすくなります。
ここが落とし穴:Entra 経由ログインだけだと「Google のアクセストークン」を手元に持っていない
ただし、UserInfo を叩くためには Google のアクセストークンが必要です。ところが Entra を認証の入り口にしていると、アプリが持っているのは Entra のトークンだけで、Google のアクセストークンがアプリに渡ってこない構成が一般的です。
この点を見落とすと、「UserInfo を呼べばいいのは分かった。でも Google のトークンがない」という状態になります。ここから先は、設計として次のどちらかを選びます。
- 設計A:認証(Entra)とプロフィール取得(Google OAuth)を分ける(二段階の連携)
- 設計B:Entra 側の仕組み(構成)で Google トークンを受け渡せるようにする(可能なら)
- 設計C:Google からの取得を諦め、アプリ側プロフィールへ寄せる
どれが正解かは、あなたの要件(写真が必須か、Google API を呼ぶ予定があるか、同意画面を許容できるか)で決まります。
設計A:サインインは Entra、プロフィールは「追加の Google OAuth」で取得する(現場で強い)
最も分かりやすく、将来拡張にも強いのがこの構成です。
流れ
- ユーザーは Entra を経由して Google でサインイン(あなたのアプリは Entra トークンを受け取る)
- アプリは「プロフィール同期/Google 連携」の導線を表示(初回ログイン直後が分かりやすい)
- ユーザーに Google OAuth 同意を行ってもらい、Google のアクセストークンを取得
- バックエンドが UserInfo を呼び、氏名・写真 URL を取得して保存
「同じ Google でログインしたのに、なぜもう一度 Google の同意が必要なのか」と思われがちですが、目的が違います。
| フェーズ | 目的 | 必要なもの | ユーザー体験 |
|---|---|---|---|
| Entra 経由サインイン | アプリにログインさせる(本人確認) | Entra が発行するトークン | 「ログイン」 |
| Google OAuth 連携 | Google の情報を読む(プロフィールやAPI) | Google アクセストークン(必要スコープ) | 「連携・許可」 |
実務では、ログインはできたがプロフィールが空だと違和感が出るため、初回ログイン時に次のような分岐を作ると自然です。
- 写真や氏名が必須:初回ログイン直後に「プロフィールを同期する」フローへ誘導
- 必須ではない:まずは最低限で利用開始し、設定画面から同期できるようにする
Google OAuth のスコープは最小限から始める
プロフィール取得だけが目的なら、スコープはむやみに増やさず、まずは OIDC の基本スコープ(openId / profile / email)に寄せるのが無難です。
| スコープ | 狙い | 取得できる可能性がある情報 | 設計メモ |
|---|---|---|---|
| openid | OpenID Connect としての認証 | sub 等の識別子 | UserInfo を使う前提の土台 |
| profile | 基本プロフィール | name, picture など | “写真が欲しい”なら基本的に必要 |
| メール情報 | email, email_verified | 登録・通知に必要なら |
「写真だけほしいのに、なぜ profile が必要?」となりがちですが、Google の基本プロフィールは profile にまとまっていることが多いため、まずは profile を付けた設計を検討します。
実装のポイント:アクセストークンは“できるだけサーバー側で扱う”
UserInfo を呼ぶための Google アクセストークンは、扱いを間違えると漏えいやすい重要情報です。特に SPA(フロントエンドのみ)で完結させる場合は保管場所が課題になります。
おすすめの原則は次の通りです。
- アクセストークンは可能ならバックエンドで受け取り、バックエンドから UserInfo を呼ぶ
- フロントに渡すのは「取得したプロフィール情報(表示名、写真URL等)」のみにする
- 必要なスコープを最小化し、後から増やす(ユーザー同意も段階的に)
設計B:Entra 側で“外部 IDP のトークン”を受け渡せるか検討する(できる場合だけ)
環境によっては、Entra(特に B2C/External ID のカスタム構成)で外部 IDP から受け取った情報やトークンを、アプリに返すクレームとして出力できるケースがあります。ただし、これは常に可能な一般解ではなく、製品・設定・セキュリティ要件に左右されます。
ここでは「検討観点」を整理します。実装に踏み込む前に、次の質問に答えられるかが重要です。
| 確認したいこと | 理由 | YES の場合 | NO の場合 |
|---|---|---|---|
| 外部 IDP(Google)のアクセストークンをアプリへ渡せる設計か | UserInfo を呼ぶには Google トークンが必要 | Entra トークンに埋める/バックエンドに渡すなど検討 | 設計A(追加 OAuth)か設計C(アプリ側プロフィール)へ |
| トークン受け渡しがセキュリティ/監査上許容されるか | 外部トークンは漏えい時の影響が大きい | バックエンド限定、短期利用、保管しない等の対策が必要 | 外部トークンを扱わない設計に寄せる |
| ユーザー同意とスコープ指定を制御できるか | profile/email の同意が必要になる | 同意画面の文言・導線も含めて設計 | アプリ側入力で完結させる |
もし「外部トークンを受け渡す」方針にするなら、次のような運用ルールをセットで決めると事故を減らせます。
- 外部アクセストークンをフロントへ配布しない(原則バックエンド限定)
- 外部アクセストークンを永続保存しない(必要なら暗号化と期限、失効導線)
- プロフィール取得後はトークンを破棄し、表示用データだけ保持する
「写真が欲しいだけ」なら、外部トークン受け渡しはオーバーキルになることも多いため、次の設計Cも現実解として強いです。
設計C:プロフィールはアプリのマスタに寄せる(いちばんトラブルが少ない)
Google の氏名・写真を“自動で”取りたい気持ちは分かりますが、運用を始めると「取得できないユーザーが一定数出る」「同意を嫌がる」「写真 URL が変わって表示が壊れる」などの現実に当たります。
そこで、認証とは切り離して、プロフィールはアプリ側のユーザーマスタを正にする設計は非常に安定します。
初回ログイン後の登録画面を用意する
おすすめの UX は「初回ログイン直後に 30 秒で終わるプロフィール登録」です。外部ログインの種類が増えても壊れません。
| 項目 | 初期値の入れ方 | ユーザーが編集できるか | 備考 |
|---|---|---|---|
| 表示名 | 取れる場合は Entra クレームを初期値に | できる | 名寄せや本人確認とは切り離す |
| プロフィール写真 | 未設定(デフォルトアイコン) | できる | アップロード/アバター選択にすると安定 |
| メール | 取れる場合は初期値に | 要件次第 | 通知先として検証プロセスを別で持つと堅い |
この設計にしておくと、「Entra から返るクレームの差分」や「Google 側の仕様変更」「写真 URL の寿命」などの外部要因に振り回されにくくなります。特に B2B/B2C をまたぐサービスや、Apple/LINE 等も並行サポートする予定がある場合は、アプリ側プロフィールマスタ化が効きます。
それでも “Entra トークンだけで” 取れる可能性がある情報と、期待値の置き方
ここまで「Entra のトークンでは Google プロフィールが取れない」と強めに書きましたが、実際には Entra 側の設定やマッピング次第で、氏名やメールなど一部は取れるケースがあります。ただし、写真(picture)まで期待すると詰まりやすい、というのが実務上の感触です。
期待値を整理するため、Entra トークンに入ってきやすい情報と、入りにくい情報を分けて考えます。
| カテゴリ | Entra トークンで入りやすい | 入りにくい/保証しづらい | 理由 |
|---|---|---|---|
| 識別子 | ユーザー ID(Entra 内部の識別) | Google の sub をそのまま | Entra が自分の世界のユーザーとして発行するため |
| 連絡先 | メール(設定や同意が整っていれば) | 複数メール、補助メール | 最小限に寄せられる |
| 表示系 | 表示名(取れても簡易) | 写真(picture) | 写真は URL 管理やプライバシーが絡みやすい |
「アプリの UI にアイコンを出したい」という要件は多い一方で、写真は本人が変更しやすく、URL が変わりやすく、外部参照の可用性にも左右されます。ここは仕様上の“保証が弱い領域”として捉え、アプリ側での保管・アップロード・デフォルト表示を用意しておくと運用が安定します。
実装例:Google UserInfo を呼ぶ(Node.js / C# のイメージ)
ここでは「Google アクセストークンが手元にある」前提で、UserInfo を呼んでプロフィールを取得する例を示します。アクセストークンの取得は、あなたのアプリ構成に合わせて(設計AまたはB)行ってください。
Node.js(fetch)
async function fetchGoogleUserInfo(googleAccessToken) {
const res = await fetch("https://openidconnect.googleapis.com/v1/userinfo", {
headers: {
Authorization: `Bearer ${googleAccessToken}`,
},
});
if (!res.ok) {
const body = await res.text();
throw new Error(`UserInfo failed: ${res.status} ${body}`);
}
const userInfo = await res.json();
// 例:必要な情報だけ取り出す
return {
googleSub: userInfo.sub,
name: userInfo.name ?? null,
pictureUrl: userInfo.picture ?? null,
email: userInfo.email ?? null,
emailVerified: userInfo.email_verified ?? null,
};
}
C#(HttpClient)
using System.Net.Http.Headers;
using System.Text.Json;
public async Task FetchGoogleUserInfoAsync(string googleAccessToken)
{
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", googleAccessToken);
var res = await http.GetAsync("https://openidconnect.googleapis.com/v1/userinfo");
var body = await res.Content.ReadAsStringAsync();
if (!res.IsSuccessStatusCode)
throw new Exception($"UserInfo failed: {(int)res.StatusCode} {body}");
using var doc = JsonDocument.Parse(body);
var root = doc.RootElement;
return new GoogleUserInfo
{
Sub = root.TryGetProperty("sub", out var sub) ? sub.GetString() : null,
Name = root.TryGetProperty("name", out var name) ? name.GetString() : null,
Picture = root.TryGetProperty("picture", out var pic) ? pic.GetString() : null,
Email = root.TryGetProperty("email", out var email) ? email.GetString() : null
};
}
public class GoogleUserInfo
{
public string? Sub { get; set; }
public string? Name { get; set; }
public string? Picture { get; set; }
public string? Email { get; set; }
}
ここで得られた picture URL は、単純に DB に保存して UI に出すだけでも動きますが、長期運用を考えると次の方針が安全です。
- picture URL はキャッシュとして扱い、取れなければデフォルトアイコンにフォールバック
- 「同期」ボタンで再取得できるようにする(URL の変化に対応)
- 外部 URL をそのまま使うのが怖いなら、画像をダウンロードして自社ストレージに保管(利用規約・プライバシー配慮が前提)
よくあるハマりどころと解決の指針
「Entra のトークンに picture を入れたい」と考え続けてしまう
まず、Entra が発行するトークンは Entra の裁量でクレームが決まります。外部プロバイダーのクレームをそのまま期待すると、検証に時間を溶かしやすいです。
指針:写真など外部プロフィール属性が本当に必要なら、Google から直接取る(UserInfo)か、アプリ側プロフィールに寄せる。
スコープが足りず、UserInfo が期待通り返らない
UserInfo はアクセストークンの権限に依存します。openId だけで行けると思い込むと、name/picture が出ないことがあります。
指針:profile/email を必要に応じて付け、同意画面でユーザーが許可したことを確認する。必要最小限から始める。
メールアドレスを一意キーにしてしまう
Google のメールはユーザーが変えられます(転送やエイリアス、変更、管理ポリシー)。メールを主キーにすると、将来の名寄せで苦労します。
指針:Google の sub、または Entra 側の不変 ID を“内部キー”として持ち、メールは属性として扱う。
写真 URL の扱いが雑で、表示が壊れる/混在コンテンツになる
外部 URL は、期限・リダイレクト・参照元制限などで突然表示できなくなる可能性があります。
指針:デフォルトアイコンを用意し、写真は“あると嬉しい”扱いにする。必要なら自社保管に切り替える。
セキュリティとプライバシーの要点(短くても必ず押さえる)
プロフィール情報の取得は UI の話に見えて、実はセキュリティとプライバシーが密接です。特にアクセストークンを扱う設計では、最低限次を守ると事故が減ります。
| 観点 | 推奨 | 避けたい |
|---|---|---|
| トークンの保管 | バックエンドで短期利用し破棄 | フロントの永続領域(localStorage 等)に長期保存 |
| スコープ | 最小限(openid/profile/email)から | 将来のために過剰な権限を最初から要求 |
| 表示名・写真の扱い | 表示用途に限定し、本人確認に使わない | 「名前が一致するから本人」などの誤った判定 |
| ユーザーへの説明 | 何を取得し、何に使うかを UI で明示 | 同意の意味が分からない導線 |
プロフィールはあくまで“表示用の便利情報”として設計し、認可・本人確認の根拠にしないことが重要です。認証の根拠はトークンの検証(署名、iss、aud、exp など)に寄せ、表示情報は別レイヤーで取り扱うのが堅牢です。
まとめ:設計の主語を「どの発行者の情報を、どのトークンで取りに行くか」に戻す
Entra ID 経由で Google サインインしているとき、アプリが見ているのは Entra のトークンです。そこに Google の氏名や写真が十分に入らないのは、設計として自然です。
Google のプロフィール(氏名・アイコン写真など)を確実に扱いたいなら、次のどれかに寄せると解決が早く、運用も安定します。
- Google のトークンを取得し、UserInfo(https://openidconnect.googleapis.com/v1/userinfo)で取る
- 写真まで含めたプロフィールはアプリ側で管理し、初回ログインで登録してもらう
- Entra のクレームは“取れる範囲”に留め、過度な期待を置かない
「Entra のトークンに Google の情報が入っていない」という現象を“バグ”として追いかけるより、必要なプロフィール情報を正しい発行者(Google)から、正しい手段(Google トークン+API)で取得する設計へ切り替えるのが、最短ルートになります。

コメント