Conditional Access Policy Plans: Fewer Policies, but Not Fewer Decisions

Conditional Access Policy Plans: Fewer Policies, but Not Fewer Decisions

Every Conditional Access tenant I have reviewed in the last few years has the same smell. Forty, sixty, sometimes well over a hundred policies, and the break glass exclusion copied into every single one of them by hand. Change the persona group, touch twenty policies. Forget one, and you find out during an incident. Microsoft has now shipped an answer to that in preview: Conditional Access policy plans.

And it is only getting worse. Global Secure Access brings its own Conditional Access policies for traffic profiles and security profiles, and agent identities add another layer on top. Microsoft names both as reasons why the policy list keeps growing, and plans are their answer to that.

I spent a morning building a best practice setup in my lab tenant, ran the migration wizard against some of my own old policies and tried to break things on purpose. Plans are a real improvement for how you author and maintain Conditional Access. They also come with one design rule that is easy to miss and changes how you should slice your plans: as soon as a plan shares anything, client app types belong to the plan, not to the rule.

So here is the honest version: what a plan actually is, how I would structure one, where the preview bites, and how to migrate without a gap in enforcement.

๐Ÿงฉ What a policy plan actually is

A plan is a container for related Conditional Access rules. It is an authoring and change management layer, not a new enforcement engine. You find it in the Entra admin center under Entra ID > Conditional Access > Policy plans (Preview), right next to the classic policy list, which also greets you with a migration banner now.

Conditional Access policies page in the Entra admin center showing the policy plans preview banner and the Migrate to Conditional Access Policy plans banner
Two banners, one message: Microsoft wants you to start moving. Do not click Get started before you have read the migration section below.

Every plan has three layers:

  • Plan details: name, description, owners and tags. Owners are for accountability only, they do not grant any edit permission. None of this affects enforcement.
  • Shared conditions: identities, resources and conditions defined once and inherited by every rule in the plan.
  • Rules: the actual enforcement scenarios. Each rule adds its own assignments and conditions plus its grant or session controls. A plan needs at least one rule.

At sign in, Entra merges each rule with the plan’s shared conditions and evaluates that rule like a standalone policy. Every rule shows up separately in the sign in logs, so your troubleshooting workflow stays the same. Classic policies keep working and are evaluated side by side with plan rules. There is no precedence between them: everything that applies must be satisfied.

The inheritance rules are where it gets interesting:

SettingPlan level (shared)Rule level
Specific users, groups, directory rolesYesYes, adds to the plan
All usersNoYes
All resourcesNoYes
ExclusionsYesYes, they accumulate
Remove a plan exclusionn/aNot possible
Narrow the plan targetingn/aNot possible
Mix users and agents or workload identitiesNoYes, one identity type per rule
Grant and session controlsNoYes, rules only

Read that table twice. The two “not possible” lines are the whole point of the feature, and also its biggest risk. An exclusion at plan level cannot be forgotten by a rule. A wrong exclusion at plan level cannot be fixed by a rule either.

๐Ÿ—๏ธ My best practice: one plan per objective

The obvious way to use plans is to group by naming prefix: all CAU policies in one plan, all CAD in another. I would not do that. A shared prefix is not a shared intent, and shared conditions only make sense when they are true for every current and future rule in the plan.

My rule of thumb: a plan is an objective, a rule is a variation. Put into shared conditions only what defines the objective. Everything that varies belongs in the rules. For the lab I built three plans, all with the break glass group as shared exclusion and every rule in report-only:

PlanShared conditionsRules
FraDa – Baseline (All Users)Exclude: break glass groupCA001: All users, all resources, authentication strength MFA
CA002: All users, all resources, block device code flow and authentication transfer
FraDa – Privileged Access (Admins)Include: Global Admin, Privileged Role Admin, Security Admin, CA Admin, Intune Admin
Exclude: break glass group
CA101: Phishing-resistant MFA
CA102: Compliant device
CA103: Sign in frequency 4 hours
FraDa – Legacy Authentication (All Users)Client app types: Exchange ActiveSync clients, Other clients
Exclude: break glass group
CA003: All users, all resources, Block

Notice what the baseline plan does not share: All users. That is not a design choice, it is the platform. All users is rule level only, so a baseline plan has an empty include at plan level and the rules carry the scope. The break glass exclusion still lives at plan level, which is exactly where I want it.

And yes, legacy authentication gets a plan of its own. Why that is not overkill but a requirement is explained further down.

Also notice the rule names. I kept my classic naming convention with IDs (CA001, CA101) on the rules. Plans add a grouping layer, they do not replace a naming scheme you can filter on in sign in logs and exports.

๐Ÿ› ๏ธ Building it step by step

1๏ธโƒฃ Plan details and shared identities

Policy plans (Preview) > Create plan. Give the plan a name that describes the objective, write the objective into the description and set tags you can filter on later. Then add the shared identities. For the baseline plan that is only the excluded break glass group.

Policy plan editor with plan name FraDa Baseline All Users, objective description, owner, tags and the break glass group as shared excluded group
The description is not decoration. Write the objective into it, because that is what tells the next admin what does and does not belong in this plan.

2๏ธโƒฃ The first rule

The wizard forces you to define the first rule right away. Two details are worth a look. The state defaults to Report-only, and the inherited exclusion is shown with a link icon and cannot be removed inside the rule.

Rule information step of a policy plan with state report-only and the break glass group shown as inherited excluded group with a link icon
The little link icon is the feature. This exclusion comes from the plan and no rule can drop it.

Then the usual Conditional Access work: All users, All resources and, my default for every MFA rule, an authentication strength instead of the plain MFA checkbox.

Policy plan rule with All users included, inherited break glass exclusion, All resources and grant control require authentication strength Multifactor authentication
All users and All resources live here, in the rule, because the plan level does not offer them at all.

Additional rules are added later on the plan itself with Add rule. For CA002 I used the newer Authentication flows condition to block device code flow and authentication transfer for everyone. Phishing with device codes is far too popular to leave that open, and it fits perfectly into a baseline plan.

3๏ธโƒฃ The privileged access plan

This is where plans really shine. The admin roles are defined once as shared included roles, together with the break glass exclusion. Every rule in this plan targets exactly the same population without anyone having to select eight roles three times.

Policy plan editor for privileged access with Global Administrator, Privileged Role Administrator, Security Administrator, Conditional Access Administrator and Intune Administrator as shared included roles and the break glass group excluded
Add a new admin role here once and all three rules pick it up. That alone is worth the migration effort for most tenants I see.

Three rules, three variations of the same objective: phishing-resistant MFA, compliant device and a four hour sign in frequency. On the plan overview you get the shared conditions on top and the rules below, including sign in statistics per rule once there is traffic.

Overview of the FraDa Privileged Access plan with owner, tags, shared included roles, shared excluded group and shared client app types All
Look at the right column: Client app types “All”. I never set it. It is there because it has to be, more on that below.
Rules list of the privileged access plan with three report-only rules for phishing-resistant MFA, compliant device and sign in frequency, including sign in statistics per rule

๐Ÿงท Client app types: one setting per plan as soon as it shares anything

My first version of the baseline plan had a third rule: block legacy authentication. Classic pattern, All users, All resources, Client app types set to Exchange ActiveSync and Other clients, Block. Inside the baseline plan I could not build it. In the rule, Client app types was greyed out and showed “All” with the inherited link icon.

Add conditions pane inside a policy plan rule with the Client app types condition greyed out and the rule showing inherited client app types All
Every other condition is available inside the rule. Client app types is not, because in this plan it comes from the plan.

That is only half of the story, though. Plans created from templates behave differently, and that sent me back into the lab. I created a test plan from two templates at once, Block legacy authentication and Require MFA for all users. You can combine as many templates as you like, and just like the migration wizard, every rule is created Off. The preview already tells you what is going on: no conditions are shared, and each rule brings its own client app types.

Create plan from templates pane with the Off state warning, shared conditions saying no conditions are shared across all selected templates, rule one blocking legacy authentication clients and rule two requiring MFA for all users with client apps All
Two templates, two different client app types, one plan. Possible, as long as the plan shares nothing.

The JSON view confirms it: sharedConditions is null, and the client app types live on the rule. In this plan, Add rule lets me pick the client app types for every rule individually.

JSON view of the FraDa Template Test plan with sharedConditions null and the first rule carrying clientAppTypes exchangeActiveSync and other
No shared conditions, no plan level client app types.

Then I did what every admin does next: I added the break glass group as shared exclusion and saved. The plan suddenly had shared conditions with client app types “All”, and I never set it. The reason sits in the Graph schema. clientAppTypes is a required property of a Conditional Access condition set. As soon as a plan has shared conditions at all, they need a client app type, and the portal fills in “All”. Remove it in Edit plan and save, and “All” comes back. Change it to another value, and it sticks.

From that moment on, client app types are locked in every rule. The legacy rule now shows the inherited “All” next to its own two legacy values, none of them can be removed, and in Add conditions the client app types entry is greyed out.

Edit rule view of the template legacy rule after adding a shared exclusion, with the inherited break glass exclusion, inherited client app types All and the rule values Exchange ActiveSync clients and Other clients without remove buttons
One shared exclusion later: inherited “All” plus the old legacy values, and nothing in here is editable anymore.

I ran this test with all rules Off, so I will not claim how the engine resolves “All” at plan level plus legacy types on the rule. Shared conditions are combined with each rule, so it should still end up as legacy only. But a mixed state that nobody can edit anymore is not something I want in a production plan.

The practical consequence: the moment a plan shares anything, client app type is a property of the whole plan. Every rule inherits it, no rule can change it. And sharing at least the break glass exclusion is the main reason to use plans in the first place.

So the legacy authentication block does not go into the baseline plan. It gets its own plan with the legacy client app types as shared condition:

Policy plan editor of FraDa Legacy Authentication with objective description, break glass group as shared exclusion and Exchange ActiveSync clients and Other clients as shared client app types
This is where the legacy decision lives: at plan level. Write the reason into the description, the next admin will wonder why a single rule needs its own plan.
Policy plan rule with All users, All resources, inherited client app types Exchange ActiveSync clients and Other clients shown with link icons and grant control Block access
The rule itself only says who, what and Block. The legacy part is inherited and locked.

One small UI quirk: the Review + create page of the wizard lists the rule with Client apps “All”. Do not let that confuse you. The rule editor shows the inherited legacy types, and the JSON view of the plan shows what is actually stored:

JSON view of the FraDa Legacy Authentication plan with tags and sharedConditions clientAppTypes set to exchangeActiveSync and other
The JSON view is also your first look at the Graph schema of plans. Worth a minute before you automate anything.

A plan with a single rule feels like overhead at first. It is not. The plan name says exactly what it does, nobody can widen the client app types by accident inside a rule, and if you later add a second legacy scenario, for example a separate scope for guests, it inherits the right client app types automatically.

๐ŸŽฏ The flip side. Changing the client app types of a plan changes every rule in it at once. While testing, my baseline plan had “(Modern) Browser” instead of “All” for a while. Result: the MFA rule for all users only covered browser sign ins, and nothing in the portal warned about it. Report-only saved me here. In production that is a silent hole.

Policy plans list filtered to the FraDa Baseline, FraDa Privileged Access and FraDa Legacy Authentication plans with owners, rule count and tags
Three plans, six rules, three objectives. Client app types decided once per plan.

My rule of thumb: decide client app types when you design a plan, not when you add a rule. “All” for baseline and persona plans, the legacy types for a legacy authentication plan, “(Modern) Browser” for browser only scenarios like session controls on unmanaged devices.

โš ๏ธ More gotchas before you go live

๐Ÿ“ Location rules and token lifetime. During the preview, plans that target users with a location condition issue one hour access tokens without the instant IP enforcement you know from continuous access evaluation. A user who leaves a trusted location can keep access for up to an hour. If your design depends on strict location enforcement, keep those rules as classic policies for now.

๐Ÿ’ฅ Bigger blast radius. The flip side of “change once, apply everywhere”: a wrong group in the shared exclusions now opens every rule in the plan at once. Treat plan level changes like a change to five policies, because that is what they are.

๐Ÿงฏ Temporary exceptions. The classic “exclude this one service account from the MFA rule for two weeks” does not belong at plan level, because there it hits every rule. Put it on the rule, and be aware that you cannot undo a plan level exclusion there.

๐Ÿ—‚๏ธ Moving rules changes what they do. Click a rule in a plan and the details pane offers Edit, Move rule, Duplicate rule and Delete. In a plan with a single rule, Move and Delete are greyed out, because a plan always needs at least one rule. That is why my legacy authentication plan shows them disabled.

Rule details pane of the rule FraDa-CA003-LegacyAuth with the actions Edit Rule, Move rule, Duplicate rule and Delete, where Move rule and Delete are disabled, plus rule details, client app types and Block control
Move and Delete are disabled here because this is the only rule in the plan.

Move is the one to be careful with. The rule leaves its old shared conditions behind and picks up the ones of the target plan: exclusions, included roles and, yes, client app types. Move my device code rule from the baseline plan into the legacy authentication plan and it would suddenly only apply to legacy protocols. The dialog lists every plan in the tenant and says nothing about that.

Select rule destination dialog for moving the rule FraDa-CA002 with the FraDa Baseline plan marked as currently viewed and the Privileged Access and Legacy Authentication plans as targets
One click, new shared conditions. Treat a move like a new rule and validate it in report-only again.

๐Ÿ’พ Backups and tooling. Plans live in their own endpoint, /beta/identity/conditionalAccess/plans. Any backup, export or drift detection that only reads conditionalAccess/policies does not see them. On top of that, a rule only stores its own settings, not the inherited ones. Back up plans and rules together or you restore a rule without its exclusions.

๐Ÿ”ข The policy limit question. After I added five rules, the Policy Snapshot on the Conditional Access overview still showed exactly the same total as before. That tells you how the overview counts, not whether rules count against the per tenant policy limit. The documentation does not answer that yet. If your tenant is close to the limit, do not assume plans buy you headroom.

๐Ÿ”„ Migrating existing policies without a gap

The migration wizard starts from the banner on the policies page: Get started, select the policies, Merge into a plan. The wizard does not suggest which policies belong together, that part is your job.

Conditional Access policy list in migration mode with two FraDa block policies selected and the button Merge 2 policies into a plan

The preview is honest about what happens. Every policy becomes its own rule, every rule is created Off, the originals stay untouched, and only conditions that are identical across all selected policies move into the shared conditions. In my test with two block policies that was exactly one thing: Client app types “All”, which is no surprise after the template test above: as soon as anything is shared, client app types come along. Identities, resources and exclusions stayed on the rules because they were not identical. If the policies you merge use different client app types, they do not belong in the same plan anyway.

Migrate to Policy plans pane with default name Migrated policy plan, a warning that all rules are created in the Off state, shared condition client apps All and two rules
The wizard only shares what is 100 percent identical. If your break glass exclusion is not the exact same group in every policy, it will not end up where you want it.

My migration sequence:

  1. Pick a small group by objective, not by prefix. Two to five policies that really share the same intent and the same client app types.
  2. Review the result, especially the exclusions. If the break glass exclusion stayed on the rules, move it to the plan manually and remove it from the rules.
  3. Set all rules to Report-only with Edit all rules and compare the report-only results in the sign in logs with the originals for at least a week.
  4. Do not touch the originals during validation, otherwise you compare against a moving target.
  5. Rules On first, originals Off second. The reverse order leaves a window without enforcement. The short overlap with double enforcement is the safer evil.
  6. Delete the originals after a validation period, with an export in your change ticket.
Edit all rules dialog of a policy plan with a confirmation checkbox and the options On, Report-only and Off
One click flips every rule in the plan. Useful for validation, dangerous on a Friday afternoon.

๐Ÿ“‹ Requirements

  • Role: Conditional Access Administrator to create, edit or delete plans. Plan ownership does not replace the role.
  • Licensing: the same as for Conditional Access itself. I have not found any additional requirement for plans.
  • Status: public preview. Graph endpoints are beta and subject to change.
  • Break glass: a dedicated, cloud only exclusion group for your emergency accounts. If you do not have one yet, fix that first, plans make it even more central.

๐Ÿ’ญ Final Thoughts

Policy plans fix a real problem. Copying exclusions and role selections into dozens of policies was always the weakest part of every Conditional Access design, and the privileged access plan alone shows how much cleaner this gets. For new tenants I would start with plans from day one, structured by objective.

For existing production tenants I would take it slower:

  • Start with one plan where the shared conditions are obvious, privileged access is the perfect candidate.
  • Give legacy authentication its own plan, and keep location based rules standalone until the preview limitations are gone.
  • Do not migrate anything your backup tooling cannot see yet.

Plans do not reduce the number of decisions you make in Conditional Access. They reduce the number of places where you can forget one.

Leave a Reply

Your email address will not be published. Required fields are marked *