Hitting the Claims Mapping Policy 50 Limit in Entra ID
Whoah…. okay, maybe this title isn’t as long as my SAML certificate notification post, but the topic sure likes to sprawl.
Let’s say you’re mid–SSO integration. The app isn’t a simple gallery walkthrough. It’s the kind that used to live on AD FS with a claim rule set that looked like a novel. You’re past NameID. Past the happy-path attributes. You’ve decided the portal Attributes & Claims blade isn’t going to cut it — too many transforms, too much repeatable config — so you reach for a Claims Mapping Policy.
You wire up the JSON. You assign it to the service principal. You test.
And then… half the claims you carefully defined just don’t show up in the assertion.
No hard error. No “you exceeded the limit” banner. Just missing attributes and a Monday morning ticket that says SSO is “randomly” broken.
Welcome to 50.
Claims Mapping Policy vs the portal (don’t mix them up)
Before we talk about the ceiling, we need to name the tool correctly.
Portal: Attributes & Claims
Enterprise app → Single sign-on → Attributes & Claims. Clicky UI. Claim conditions. Group claims. Great for most SaaS apps.
There’s also Custom Claims Policy (Preview) in Microsoft Graph, which shares the same underlying config as that portal blade — so you can manage those claims via Graph or the admin center.
Claims Mapping Policy (CMP)
A first-class policy object (claimsMappingPolicy) you create in Graph/PowerShell and assign to a service principal. One policy can be linked to multiple apps. You define ClaimsSchema, optional ClaimsTransformation / ClaimsTransformations, optional GroupFilter, IncludeBasicClaimSet, and friends in JSON.
Microsoft is blunt about precedence:
Claims Mapping Policy supersedes both Custom Claims Policy and the claims customization offered through the Microsoft Entra admin center.
Once a CMP is assigned, tokens for that app ignore the portal / Custom Claims Policy config. The Attributes & Claims blade often shows something like “This configuration was overwritten by a claim mapping policy created via Graph/PowerShell.” That’s not a bug — that’s the policy winning.
Notes:
- CMP is great when you need automation, reusable policy, or claim shapes the UI doesn’t express cleanly.
- CMP is not great if you still want colleagues editing the same map in the portal later. Pick a source of truth and stick to it.
Docs: Claims customization · Claims customization using PowerShell · claimsMappingPolicy resource
The 50 that bites: ClaimsSchema
Here’s the limit that stopped me cold on a recent SSO integration.
From the Graph claimsMappingPolicy docs:
| Policy property | Documented max | What happens over the limit |
|---|---|---|
| ClaimsSchema | 50 claim schema entries | Entries after 50 are ignored — not emitted |
| ClaimsTransformation | 50 transformations | Transformations after 50 are ignored |
Read that again: ignored. Not rejected on create. Not called out in the token with a friendly overage claim. Just… absent.
So if you migrate a chunky AD FS rule set into one giant ClaimsSchema array — employee ID, department, cost center, site code, three flavors of NameID logic, a dozen app-specific URIs, plus intermediate schema entries that only exist to feed transformations — you can sail past 50 without the create API yelling at you. The policy saves. The sign-in “works.” The 51st claim never leaves the building.
That’s the failure mode. Silent truncation of your carefully authored map.
Why 50 arrives faster than you think
A few ways the schema budget evaporates:
- One AD FS issuance rule ≠ one Entra schema entry. Transforms often need an input schema entry and an output schema entry that points at
Source: transformation. - Duplicates. Emitting the same user attribute under two SAML URIs still costs two schema rows.
- “Just in case” claims. Porting every historical AD FS claim “because the vendor might need it” is how you invent a limit incident.
- Basic claim set confusion.
IncludeBasicClaimSet: truestill emits the basic set in addition to your schema — it does not give you free schema slots. Setting itfalsemeans you must put anything you still need intoClaimsSchemayourself (and those count).
The other ceilings (still real, still adjacent)
CMP’s 50 isn’t the only quota in this neighborhood. Keep them straight so you don’t debug the wrong wall:
| Feature | Limit / rule | Why you care |
|---|---|---|
| CMP ClaimsSchema | 50 entries; extras ignored | This post’s villain |
| CMP ClaimsTransformation | 50 transforms; extras ignored | Same silent failure mode |
| Portal / SAML regex transforms | Max 20 total regex replacements | Documented under SAML claims customization |
| Portal claim conditions – groups | 50 unique groups across all claims on the app | Different “50” — conditional claim sources |
| SAML groups claim | 150 memberships (incl. nested in the general case) | Missing groups / overage |
| JWT groups claim | 200 | Same problem, higher ceiling |
| Group filtering | Only if user in ≤ 1,000 groups | Filter skipped → overage |
| Groups assigned to the app | Direct membership only | Nesting surprises |
| Portal transforms per claim | Max 2 chained | UI constraint |
Group claims and claim conditions still matter on SSO projects — especially if the app wants a groups dump and a CMP. Just don’t conflate “50 unique groups for claim conditions” with “50 ClaimsSchema entries on a CMP.” Same number. Different knobs.
Docs: Entra service limits · Configure group claims · Customize SAML token claims
How CMP talks to groups
CMP can include a GroupFilter in the policy definition: match on displayname or samaccountname, with prefix / suffix / contains. Useful for shrinking what goes into the groups claim.
That filter is still subject to the broader group-claim rules (150 SAML / 200 JWT, filter eligibility ≤ 1,000 memberships, assigned-groups = direct only). CMP doesn’t give you a free pass around those.
If you’re doing multi-pattern group filtering, the native filter is effectively one pattern string — regex-style transforms on a customized group claim name are the usual escape hatch (and then you’re back in regex-replacement budget land).
Assigning the policy (the part people forget to verify)
High-level flow:
- Create the policy:
New-MgPolicyClaimMappingPolicy(orPOST /policies/claimsMappingPolicies). - Assign it to the enterprise app’s service principal:
New-MgServicePrincipalClaimMappingPolicyByRef. - Confirm assignment:
GET .../servicePrincipals/{id}/claimsMappingPolicies. - For APIs/OIDC resource apps, also mind
acceptMappedClaims/ token version on the application object when you’re emitting mapped claims into access tokens. - Test the actual token (SAML assertion or JWT at jwt.ms). Do not trust the Attributes & Claims blade as a mirror of the CMP — it isn’t one.
Scopes you’ll need typically include Policy.ReadWrite.ApplicationConfiguration (and related read scopes). Claims-mapping policies can only be assigned to service principals — not set as an org-wide default.
Docs: Claims customization using PowerShell · Customize claims with CMP in Microsoft Graph
What breaks in the wild
Patterns that look a lot like “SSO is flaky” but are really quota-shaped:
- Claims 51+ silently missing → ClaimsSchema over 50; policy created fine; assertion incomplete.
- Transforms that “don’t run” → past the 50 transformation entries, or the output schema row that should emit them got truncated.
- Portal edits do nothing → CMP is assigned and superseding; you’re editing a ghost UI.
- AD FS lift-and-shift blowouts → every historical issuance/transform rule dumped into one JSON → 50 arrives on day one.
- Groups still empty / overage → you fixed CMP, but the user crossed 150 (SAML) or the app ignored overage / can’t call Graph.
- “Groups assigned to the app” + nesting → user nested under an assigned group; direct-only rule bites.
Practical ways out when you hit 50
If the CMP ClaimsSchema wall is the problem, these are the moves that actually help:
- Count before you commit. Script a check: length of
ClaimsSchemaandClaimsTransformationarrays. Treat 50 as a hard design budget, not a surprise. - Consolidate. One claim with a clear URI beats three legacy aliases “for compatibility” unless the SP truly needs all three.
- Drop dead weight. If the vendor’s SAML tracer never consumed a claim in UAT, it doesn’t need a schema row in prod.
- Prefer app roles for authorization when the app can take them — smaller tokens, clearer semantics, less pressure to invent claim sprawl (nested groups still aren’t in play for app-role assignment via groups).
- Emit Object IDs for groups, not display names, when groups are still required — and prefer groups assigned to the application so you stay intentional under 150.
- Simplify transforms. Every Join/Extract/RegexReplace chain tends to cost schema + transformation slots. Sometimes a synced extension attribute computed upstream is cheaper than three transforms in the token pipeline.
- Don’t duplicate schema entries for the same source just to rename things twice — pick the claim type the app documents.
- When the app can call Microsoft Graph, let it fetch full membership / profile data instead of stuffing the universe into SAML.
- Split responsibility thoughtfully. One reusable CMP across similar apps is fine; one monster policy that tries to satisfy five unrelated vendors is how you meet 50 with style.
- Stay on CMP or stay on the portal — not both. If you need GUI + Graph on the same config, look at Custom Claims Policy (Preview) instead of CMP; know that CMP will keep winning (and locking the blade) if it’s assigned.
Quick reference
| Question | Short answer |
|---|---|
| What’s the CMP schema limit? | 50 ClaimsSchema entries |
| What’s the CMP transform limit? | 50 ClaimsTransformation entries |
| What happens at 51? | Silent ignore — not in the token |
| Does CMP override the portal? | Yes — supersedes Attributes & Claims and Custom Claims Policy |
| Where do you assign it? | To the app’s service principal |
| Is portal claim-conditions “50 groups” the same limit? | No — related neighborhood, different feature |
| SAML groups ceiling? | 150 (JWT 200) |
Final thoughts
Claims Mapping Policy is powerful. It’s also quota-shaped — and unlike a certificate expiry, it won’t email you 60 / 30 / 7 days before you have a bad time.
If you treat Entra like “AD FS but in the cloud, paste every issuance rule into JSON,” you’ll meet 50 the hard way: a policy that saves, a sign-in that “works,” and an assertion that’s missing the one attribute the app actually authorizes on.
If you treat ClaimsSchema like a budget — count entries, consolidate early, prefer app roles or Graph where you can, and keep transforms intentional — your SSO integrations stay about coffee instead of hunting ghosts in a SAML tracer.
Stay curious. Count your schema entries. And maybe don’t port the 2008 claim rule named EmitEverythingIncludingTheKitchenSink without a second look.
References
- claimsMappingPolicy resource type (ClaimsSchema / ClaimsTransformation = 50; extras ignored)
- Claims customization (CMP supersedes portal / Custom Claims Policy; GroupFilter; transforms)
- Claims customization using PowerShell and Claims Mapping Policy (create + assign to SP)
- Customize claims with Claims Mapping Policy in Microsoft Graph
- Customize SAML token claims (claim conditions 50 groups; regex max 20; portal transforms)
- Configure group claims
- Entra service limits
- SAML token claims reference
- Group claims, overage, and app roles