Entra ID(旧 Azure AD)のSAMLエンタープライズアプリで「ユーザー属性とクレーム(Attributes & Claims)」を設定しているのに、Microsoft Graph/PowerShellで取得しようとすると空になる――この“あるある”は、取得先のAPIが違うことが原因です。この記事では、claimsMappingPoliciesとclaimsPolicy(Custom Claims Policy)の違いを整理し、読み取り・棚卸しの具体例までまとめます。
現象の整理:「ポータルでは見えるのに、Get-Mg…は空」
SAMLのエンタープライズアプリ(サービス プリンシパル)で、Microsoft Entra 管理センター(旧 Azure ポータル)上の「ユーザー属性とクレーム(Attributes & Claims)」には設定が見えているのに、PowerShell/Microsoft Graph で次のように取得すると空(0件)になることがあります。
- PowerShell(例):
Get-Mg(Beta)ServicePrincipalClaimMappingPolicy - Graph REST(例):
GET /servicePrincipals/{id}/claimsMappingPolicies
スコープに Application.Read.All / Policy.Read.All を付けているのに空で、「APIが壊れているのでは?」と疑ってしまうパターンです。
最初に結論:claimsMappingPolicies は「割り当てた Claims Mapping Policy」しか返さない
claimsMappingPolicies(PowerShellの Get-Mg(Beta)ServicePrincipalClaimMappingPolicy など)が返すのは、サービス プリンシパルに“割り当てられている” claimsMappingPolicy オブジェクトだけです。ドキュメント上も「assigned to a servicePrincipal(サービス プリンシパルに割り当てられたもの)」を列挙すると明記されています。citeturn4view0turn4view1
つまり、ポータルの「ユーザー属性とクレーム」で設定していても、それが Claims Mapping Policy として割り当てられていない構成なら、claimsMappingPolicies が空になるのは正常です。これはMicrosoft Q&Aでも同じ整理で回答されています。citeturn8view0
混乱の原因:「ユーザー属性とクレーム」には“2つの管理系統”がある
現在のEntra IDでは、トークン(SAML/OIDC/OAuthなど)に出すクレームを管理する仕組みが大きく2系統あります。
| 系統 | Graph/PowerShellの主な取得先 | 特徴 | よくある用途 |
|---|---|---|---|
| Claims Mapping Policy | GET /servicePrincipals/{id}/claimsMappingPolicies Get-MgServicePrincipalClaimMappingPolicy / Get-MgBetaServicePrincipalClaimMappingPolicy | ポリシー(claimsMappingPolicy)を作成してサービス プリンシパルに割り当てる方式。 割り当てたものだけ取得される。citeturn4view0turn4view1 | 複数アプリで同じポリシーを使い回す、変換ルールを含む高度な定義を管理する。 |
| Custom Claims Policy(Preview) | GET /beta/servicePrincipals/{id}/claimsPolicy Get-MgBetaServicePrincipalClaimPolicy | サービス プリンシパルに1つだけ紐づく“カスタムクレーム”のポリシー。 管理センターのクレーム設定(claims customization)と同じ基盤を使う、という位置づけ。citeturn2view4turn2view5 Graphの /beta で提供され、仕様変更の可能性あり。citeturn2view0turn2view1 | ポータルで編集している「ユーザー属性とクレーム」をAPIで読み取り・更新したい(ただしbeta前提)。 |
この“二系統”を区別せずに「ポータルで見える=claimsMappingPoliciesで取れるはず」と考えると、空結果でハマります。
Claims Mapping Policy を読み取る:claimsMappingPolicies(v1.0でも可)
まずは、従来からある Claims Mapping Policy 側です。ここで取得できるのは「割り当て済みポリシーのみ」ですが、その前提を理解しているなら棚卸しに非常に向いています。citeturn4view0turn4view1
Graph REST での取得(割り当て済みのみ)
サービス プリンシパルは Object ID(id)でも、Application (Client) ID(appId)でも指定できます。citeturn4view1
GET https://graph.microsoft.com/v1.0/servicePrincipals/{id}/claimsMappingPolicies
GET https://graph.microsoft.com/v1.0/servicePrincipals(appId='{appId}')/claimsMappingPolicies
必要な権限の組み合わせはドキュメントの「Permissions」に従って付与してください(最小権限の組み合わせが提示されています)。citeturn4view1
PowerShell(Microsoft Graph PowerShell SDK)での取得
Get-MgBetaServicePrincipalClaimMappingPolicy の説明も「assigned to a servicePrincipal」と明記されており、このコマンドが“ポータルの表示そのもの”を返すわけではないことが読み取れます。citeturn4view0
# 例:割り当て済みの claimsMappingPolicy を取得
Import-Module Microsoft.Graph.Applications
Connect-MgGraph -Scopes "Policy.Read.All","Application.ReadWrite.All"
$spId = "<サービスプリンシパルのObject ID>"
$assigned = Get-MgServicePrincipalClaimMappingPolicy -ServicePrincipalId $spId
$assigned | Select-Object Id, DisplayName, Definition
claimsMappingPolicy は定義(definition)がJSON文字列として入るため、エクスポート時はそのままJSONで保持する運用が楽です(CSVに入れる場合はエスケープに注意)。
「空だった」ケースでまず確認すること
| 確認ポイント | 見方 | 判断 |
|---|---|---|
| claimsMappingPolicy を割り当てているか | Graph/PSで .../claimsMappingPolicies を叩く | 0件なら「割り当てがない」可能性が高い(正常)。citeturn4view0turn4view1 |
| そもそも狙っているのは“どの設定”か | ポータルの「ユーザー属性とクレーム」なのか、ポリシー割り当てなのか | ポータル表示=claimsMappingPolicies とは限らない |
ポータルの「ユーザー属性とクレーム」を読み取る:claimsPolicy(Custom Claims Policy / beta)
ここがアップデートの要点です。以前は「ポータルで設定している『ユーザー属性とクレーム』をそのままGraph/PowerShellで読むのは不可」とされていましたが、後に Custom Claims Policy(Preview) という形で、Graph(beta)から取得できるルートが整備されています。citeturn8view0turn2view4turn2view5
公式の整理としても、Custom Claims Policy(Preview)は、管理センターの claims customization(SAMLのAttributes & Claimsを含む)と「同じ基盤(same underlying policy)」を使い、管理場所をポータルとGraph/PowerShellで行き来できる、という位置づけです。citeturn2view4
Graph REST(beta)での取得
GET https://graph.microsoft.com/beta/servicePrincipals/{servicePrincipalId}/claimsPolicy
このエンドポイントは /beta 配下で提供され、ドキュメントでも「betaは変更され得る」「本番利用は推奨されない」と明示されています。運用ルール(検証→適用→監視)を決めて扱うのが安全です。citeturn2view0turn2view1
レスポンスの中核は claims 配下で、どのクレームをどのソースから出すか、といった構成が入ります(includeBasicClaimSet などの制御もここ)。citeturn4view6turn2view5
PowerShell での取得(Microsoft.Graph.Beta)
PowerShellでは Microsoft.Graph.Beta.Applications の Get-MgBetaServicePrincipalClaimPolicy を使うのが分かりやすいです。必要権限として Policy.Read.ApplicationConfiguration(または上位権限)が提示されています。citeturn6view1
# 例:Custom Claims Policy(claimsPolicy)を取得
Install-Module Microsoft.Graph.Beta -Scope CurrentUser
Import-Module Microsoft.Graph.Beta.Applications
Connect-MgGraph -Scopes "Policy.Read.ApplicationConfiguration"
$spId = "<サービスプリンシパルのObject ID>"
$policy = Get-MgBetaServicePrincipalClaimPolicy -ServicePrincipalId $spId
$policy | Select-Object Id, IncludeBasicClaimSet, IncludeApplicationIdInIssuer, AudienceOverride, Claims
また、Graph SDKは既定で v1.0 を使うため、betaのAPIや型を扱う場合は「betaを使う」前提が必要です(この注意は公式にも明記されています)。citeturn5view0
「GETしたら404/空」になったときの考え方
claimsPolicy は“常に存在する設定”というより「Custom Claims Policy を持っている場合に取得できる設定」です。次の切り分けで原因を特定しやすくなります。
| 症状 | 可能性が高い原因 | 対処の方向性 |
|---|---|---|
.../claimsMappingPolicies が空 | Claims Mapping Policy を割り当てていない | 必要ならポリシーを作成・割り当てる(割り当てなければ空は正常)。citeturn4view0turn4view1 |
.../claimsPolicy が取得できない/存在しない | Custom Claims Policy(Preview)がまだ作成されていない、またはbetaの取り扱い/権限が不足 | 権限(Policy.Read.ApplicationConfiguration等)とbeta利用を再確認。citeturn6view1turn5view0 |
| ポータルでクレーム編集できない/表示がロックされる | Claims Mapping Policy を利用している場合、ポータルのclaims customizationと競合することがある | どちらの方式で管理するかを統一する(後述)。citeturn2view4turn4view3 |
どっちを読めばいい?目的別のおすすめ
「読み取りたいもの」が何かで、正解のAPIが変わります。迷ったら、次の基準が実務では一番ブレません。
| 目的 | おすすめ | 理由 |
|---|---|---|
| “ポータルのAttributes & Claims相当”を取りたい(SAMLのクレーム棚卸し) | claimsPolicy(Custom Claims Policy / beta) | ポータルのclaims customizationと同じ基盤で管理できる整理が公式に示されているため。citeturn2view4turn2view5 |
| 複数アプリで同じ定義を使い回す/ポリシーで統制したい | claimsMappingPolicies | 1つのclaimsMappingPolicyを複数アプリへ割り当てる運用に向く。citeturn2view4turn4view3 |
| betaを避けたい(仕様変更リスクを取りたくない) | claimsMappingPolicies | claimsMappingPolicies は v1.0 のAPI/コマンドが用意されている。citeturn4view0turn4view1 |
実装例:全SAMLエンタープライズアプリを棚卸ししてCSV化する
「どのアプリが何を出しているか」を一覧化したい場合は、(1) SAMLアプリの抽出、(2) claimsMappingPolicies と claimsPolicy の両方を見に行く、(3) 見つかった方を“そのアプリの実設定”として記録、の順に進めるのが実務的です。
ステップ:SAMLアプリ(サービス プリンシパル)を集める
サービス プリンシパルには preferredSingleSignOnMode というプロパティがあり、値として password / saml / external / oidc が定義されています。citeturn4view2
ただし実データとしては、古いアプリや一部のケースで preferredSingleSignOnMode が空になることがある、という報告もあるため、これだけに依存して抽出すると漏れる可能性があります。citeturn7view0
そのため、最初は以下のように「まずは取れるものを取る(= preferredSingleSignOnMode が saml のもの)」を基本にしつつ、漏れが疑われる場合はタグ・命名規則・管理対象アプリの台帳など、別の情報源と突き合わせるのがおすすめです。
棚卸しスクリプト例(読み取り専用)
以下は「claimsMappingPolicies(v1.0)」と「claimsPolicy(beta)」を両方確認して、取れた方をCSVに落とす例です。環境によって必要スコープが変わるため、ドキュメントのPermissionsに従って調整してください。citeturn4view1turn6view1
# --- モジュール ---
Import-Module Microsoft.Graph.Applications
Import-Module Microsoft.Graph.Beta.Applications
# --- 認証(読み取りが目的の例。必要に応じて追加) ---
Connect-MgGraph -Scopes @(
"Application.Read.All",
"Policy.Read.All",
"Policy.Read.ApplicationConfiguration"
)
# --- サービスプリンシパル取得(まずはsamLのものを優先) ---
$servicePrincipals = Get-MgServicePrincipal -All -Property Id,AppId,DisplayName,PreferredSingleSignOnMode
$samlSps = $servicePrincipals | Where-Object { $_.PreferredSingleSignOnMode -eq "saml" }
# --- 結果格納 ---
$rows = foreach ($sp in $samlSps) {
# 1) Claims Mapping Policy(割り当てが無ければ空)
$cmp = $null
try {
$cmp = Get-MgServicePrincipalClaimMappingPolicy -ServicePrincipalId $sp.Id -ErrorAction Stop
} catch {
$cmp = $null
}
# 2) Custom Claims Policy(beta)
$ccp = $null
try {
$ccp = Get-MgBetaServicePrincipalClaimPolicy -ServicePrincipalId $sp.Id -ErrorAction Stop
} catch {
$ccp = $null
}
# JSONとして保持(CSVに入れるため軽く整形)
$cmpJson = if ($cmp) { ($cmp | ConvertTo-Json -Depth 20) } else { "" }
$ccpJson = if ($ccp) { ($ccp | ConvertTo-Json -Depth 50) } else { "" }
[pscustomobject]@{
DisplayName = $sp.DisplayName
ServicePrincipalId = $sp.Id
AppId = $sp.AppId
PreferredSingleSignOnMode = $sp.PreferredSingleSignOnMode
ClaimsMappingPolicyCount = @($cmp).Count
HasClaimsPolicy = [bool]$ccp
ClaimsMappingPoliciesRaw = $cmpJson
ClaimsPolicyRaw = $ccpJson
}
}
# --- 出力 ---
$rows | Export-Csv -Path ".\entra-saml-claims-inventory.csv" -NoTypeInformation -Encoding UTF8
棚卸しの目的が「人が読むこと」なら、CSVの列に“生JSON”を入れるより、別途 .json をアプリごとに吐き出す(Out-File で保存)運用の方が保守しやすいです。逆に「差分監視(Gitで管理)」が目的なら、JSON保存の方が適しています。
落とし穴:appId と Service Principal の Id を混同しない
クレーム設定の取得は、基本的に サービス プリンシパル(Enterprise Application)のObject ID を使って行います。claimsMappingPolicies は appId でも指定できる一方、運用上は “同名アプリ” や “マルチテナント/複数インスタンス” で混乱しやすいので、棚卸しではObject ID(servicePrincipalId)基準が安全です。citeturn4view1
運用のコツ:方式を混在させない(混ざると調査が難しくなる)
公式の整理では、Claims Mapping Policy と Custom Claims Policy(Preview)はいずれも「クレームをカスタマイズする手段」ですが、運用上はどちらを“正”として管理するかを決めておくことが重要です。Claims Mapping Policy を採用すると、ポータルの claims customization での編集と競合し得る(= UI側で編集できない/期待通りに反映されない)という注意もあります。citeturn2view4turn4view3
おすすめの決め方は以下です。
- アプリごとに個別要件が強く、ポータル運用も残したい → Custom Claims Policy(Preview) を前提にAPI連携(betaのリスクは許容する)citeturn2view4turn2view5
- 同一ルールを複数アプリへ横展開したい、IaCで統制したい → Claims Mapping Policy を採用し、割り当てと定義をコード管理 citeturn4view3turn4view1
補足:ポータルでの操作場所(読者の確認用)
SAMLトークンのクレーム(Attributes & Claims)は、管理センター上では次の手順で確認・編集します。citeturn5view1
- Entra ID → Enterprise apps → 対象アプリ → Single sign-on → Attributes & Claims
「ポータルの画面とAPI結果が一致しない」場合は、まず “どの方式(claimsMappingPolicies / claimsPolicy)で管理している状態か” を確認すると、原因が最短で特定できます。
まとめ
claimsMappingPoliciesは サービス プリンシパルに割り当てた Claims Mapping Policy だけを返すので、割り当てがなければ空でも正常。citeturn4view0turn4view1- ポータルの「ユーザー属性とクレーム」をAPIで扱いたい場合は、Custom Claims Policy(Preview)を
/beta/servicePrincipals/{id}/claimsPolicyで取得する整理が有力。citeturn2view4turn2view5turn5view0 - 棚卸しは、両方のエンドポイントを確認して「どちらで管理しているか」を記録すると後工程(移行、標準化、監査)が一気に楽になる。

コメント