Entra ID Conditional Access rollout: layering, exclusions, report-only
For developers and platform engineers who need to design Conditional Access without locking everyone out. This guide shows how to layer baseline and targeted Entra ID policies, handle exclusions deliberately, and use report-only mode plus sign-in logs to validate behavior before enforcement.
TL;DR — Treat Entra ID Conditional Access as a deny/require decision engine that runs after primary authentication but before token issuance. The safest design is a small set of layered policies: one break-glass exclusion strategy, one baseline policy set, and a few targeted policies for risky apps/users/locations, all validated in report-only mode against real sign-in logs before you flip any policy to On. Reading time: ~7 min
What it is and where it sits
Conditional Access (CA) is the policy layer in Entra ID that decides whether a sign-in gets a token, gets blocked, or must satisfy extra controls such as MFA, compliant device, approved client app, or sign-in risk requirements. For an engineer, the important mental model is not "security feature" but "policy evaluation stage in the identity pipeline."
It sits between successful credential validation and token issuance. That means it does not replace your app's authorization logic, API scopes, or RBAC. It also does not replace app-side session controls. It replaces ad hoc per-app access rules with a central decision point tied to identity, device, network, workload, and risk signals.
Typical request flow:
User/App -> App redirects to Entra ID -> Primary auth succeeds
-> Conditional Access evaluates assigned policies
-> If requirements met: token issued
-> App/API validates token and applies app authorization
More concretely:
[Browser/mobile client]
|
v
[OIDC/SAML app] -- redirect --> [Entra ID sign-in]
|
+--> user/group targeting
+--> app targeting
+--> location/device/client app conditions
+--> risk/session controls
v
[CA result: allow | require controls | block]
|
v
[ID/Access token issued]
|
v
[App/API authorization layer]
What talks to it:
- Interactive user sign-ins to Microsoft 365, Azure portal, enterprise apps, and custom apps federated to Entra ID.
- Non-interactive flows are trickier: service principals, managed identities, legacy protocols, and some device flows may not behave the way a user-centric policy author expects.
- Sign-in logs and reporting APIs are your feedback loop during rollout.
What it replaces:
- Scattered app-specific MFA toggles.
- Network-only trust assumptions like "inside VPN means trusted."
- Manual exceptions buried in app config.
What it does not replace:
- App authorization decisions.
- Privileged Identity Management workflows.
- Endpoint compliance tooling itself; CA only consumes the signal.
How it actually works
The mechanism is: target sign-ins, evaluate all applicable policies, combine results, then either block or require all grant controls from the policies that apply. In practice, your design lives or dies on policy overlap.
One realistic end-to-end example
Goal: require MFA for all users signing into cloud apps, require compliant device for the Azure admin portals, block legacy authentication, and exclude two emergency access accounts.
Step 1: user browses to Azure portal.
- App: "Microsoft Azure Management"
- User:
alice@corp.example - Device: Windows laptop, enrolled and compliant
- Network: home IP, not trusted
- Client: browser
Step 2: Entra ID authenticates Alice with password + existing session context.
Step 3: CA evaluates assigned policies.
Suppose you have these four policies:
CA00-Block-Legacy-AuthCA10-Require-MFA-All-Users-All-Cloud-AppsCA20-Require-Compliant-Device-Azure-AdminCA99-BreakGlass-Excludedis not a real policy; it is your design rule that emergency accounts are excluded from all user-targeted policies.
Evaluation outcome:
CA00does not apply because browser auth is not legacy auth.CA10applies: all users, all cloud apps, browser, any location -> grant control requires MFA.CA20applies: app is Azure management, user is in admins group -> grant control requires compliant device.- Alice is not excluded.
Combined result: token is issued only if both MFA and compliant device requirements are satisfied.
If her device were non-compliant, the sign-in is blocked even if MFA succeeds.
In sign-in logs, the shape you are looking for is roughly:
User: alice@corp.example
Application: Microsoft Azure Management
Conditional Access: Failure
Failure reason: Grant controls not satisfied
Applied policies:
- CA10-Require-MFA-All-Users-All-Cloud-Apps -> Success
- CA20-Require-Compliant-Device-Azure-Admin -> Failure
- CA00-Block-Legacy-Auth -> Not applied
If the same user connected with IMAP using basic auth to Exchange Online, the log shape changes:
User: alice@corp.example
Client app: Other clients, IMAP
Conditional Access: Failure
Failure reason: Access has been blocked due to Conditional Access policies.
Applied policies:
- CA00-Block-Legacy-Auth -> Failure
- CA10-Require-MFA-All-Users-All-Cloud-Apps -> Not applied or not satisfiable for legacy client
The practical lesson: use one policy to kill legacy auth outright, because trying to "require MFA" for protocols that cannot do modern auth leads to confusion and partial coverage.
Why layering matters
A good CA design usually has three layers:
-
Global hygiene policies
- Block legacy auth
- Require MFA for broad user populations
-
Sensitive-surface policies
- Admin portals
- High-value apps
- Risk-based sign-ins
- Device compliance for privileged access
-
Exception model
- Emergency access accounts only
- Possibly a tightly governed service account carve-out if the sign-in is truly user-like and cannot be modernized yet
Do not create dozens of nearly identical policies per app unless you need different grant controls. Every overlap increases debugging cost.
When to use it (and when not to)
| Scenario | Recommendation |
|---|---|
| You use Entra ID for workforce sign-in to SaaS/custom apps and want centralized MFA/device/risk controls | Use CA. This is the core use case. |
| You need to phase in MFA without breaking users | Use CA with report-only first, then staged enforcement by group. |
| You want to protect admin access more strongly than normal user access | Use layered CA: baseline MFA for all, stronger device/risk rules for admin roles/apps. |
| You only need app-level authorization like roles/claims inside one custom app | You probably don't need CA for that; do it in the app. |
| You have mostly service-to-service auth using managed identities/service principals | CA is not your main control plane; use workload identity protections, secret/cert hygiene, and RBAC. |
| You still rely on legacy mail/protocol clients | Use CA to block them, but expect breakage. Inventory first. |
| You want network-only allowlisting as your primary control | Don't. CA can include location, but identity + device is the stronger pattern. |
You probably don't need a complicated CA program if your environment is tiny, has no privileged role separation, and every app already enforces phishing-resistant MFA through another identity provider. In that case, complexity may outweigh the benefit.
Trade-offs
| Benefit | Cost |
|---|---|
| Centralized access policy across apps | Centralized blast radius if you mis-scope a policy |
| Stronger controls for admins and risky sign-ins | More policy interactions to reason about and test |
| Gradual rollout with report-only | Report-only predicts impact but does not perfectly reproduce every user edge case |
| Device-based access decisions | Dependency on device registration/compliance pipelines; helpdesk load when devices drift |
| Blocking legacy auth | Immediate compatibility break for old clients, scripts, and embedded devices |
| Fewer per-app security knobs | More lock-in to Entra ID policy semantics and logging |
Operationally, the hidden cost is troubleshooting. When users say "login is broken," you need a repeatable way to answer: which policy applied, which condition matched, and which grant control failed?
In practice
Example 1: Policy naming and rollout plan
Use a naming convention that encodes intent and order. This sounds boring until you are diffing sign-in logs at 2 AM.
CA00-Block-Legacy-Auth
CA10-Require-MFA-All-Users-All-Cloud-Apps
CA20-Require-MFA-Admins-All-Cloud-Apps
CA21-Require-Compliant-Device-Azure-Admin
CA30-Require-MFA-External-Networks
CA90-ReportOnly-New-HR-App
What it does: gives you a stable mental model for baseline vs targeted policies and makes logs readable.
Gotcha: the numeric prefix does not imply execution order. CA evaluates applicable policies by engine logic, not by your name sorting. The prefix is for humans.
Example 2: Microsoft Graph policy shape for report-only
If you automate CA, use Microsoft Graph rather than hand-clicking everything. The exact schema can evolve, so validate against current Graph docs before applying.
{
"displayName": "CA10-Require-MFA-All-Users-All-Cloud-Apps",
"state": "enabledForReportingButNotEnforced",
"conditions": {
"users": {
"includeUsers": ["All"],
"excludeUsers": ["11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222"]
},
"applications": {
"includeApplications": ["All"]
},
"clientAppTypes": ["browser", "mobileAppsAndDesktopClients"]
},
"grantControls": {
"operator": "OR",
"builtInControls": ["mfa"]
}
}
What it does: targets all users and all cloud apps, excludes two emergency accounts, and runs in report-only mode.
Gotcha: excluding by individual user object IDs works, but group-based exclusions are easier to govern. Keep the emergency account group tiny and audited; broad exclusion groups become a shadow allowlist.
A typical create call shape with Graph looks like:
curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies \
-d @ca10-report-only.json
Typical failure shapes to expect during automation:
HTTP/1.1 401 Unauthorized
{"error":{"code":"InvalidAuthenticationToken","message":"Access token is empty or invalid."}}
HTTP/1.1 403 Forbidden
{"error":{"code":"Authorization_RequestDenied","message":"Insufficient privileges to complete the operation."}}
HTTP/1.1 400 Bad Request
{"error":{"code":"BadRequest","message":"Invalid value specified for property 'state'."}}
The first usually means your token audience/scope is wrong or expired. The second means the caller lacks the required directory permissions/role. The third is usually a schema mismatch between your payload and the API version.
Example 3: Safe rollout sequence
⚠️ A mis-scoped Conditional Access policy can lock out admins and automation paths. Create and test emergency access accounts before changing any broad user-targeted policy.
# Pseudocode checklist, not a shell script
1. Create 2 emergency access accounts with long random passwords stored offline.
2. Exclude those accounts (or one dedicated emergency group) from every user-targeted CA policy.
3. Create CA00 block-legacy-auth in report-only.
4. Create CA10 broad MFA in report-only.
5. Create admin-specific CA20/CA21 in report-only.
6. Wait for real sign-in traffic; review sign-in logs for 3-7 days.
7. Fix false positives: service accounts, legacy clients, unmanaged admin devices.
8. Turn on CA00 first, then CA20/CA21, then CA10.
9. After each change, test: normal user, admin user, external network, mobile client.
What it does: minimizes blast radius by enforcing the least ambiguous and highest-value policies first.
Gotcha: report-only is only useful if you generate representative traffic. If nobody signs into the admin portal during the test window, your admin policy is still unproven.
Exclusions: the rule most teams get wrong
Use exclusions for exactly three things:
- Emergency access accounts
- Transitional exceptions with an owner and expiry date
- Rare technical incompatibilities you cannot yet remediate
Do not exclude whole departments because rollout is inconvenient. Put them in a staged include group instead. Inclusion by pilot groups is easier to reason about than giant global policies with sprawling exclusions.
A practical pattern:
- Baseline policies target a
CA-Baseline-Includedgroup, notAll users, during pilot. - After validation, switch to broad targeting and keep only emergency exclusions.
- Sensitive policies target admin role assignments or dedicated admin groups.
Further reading
- Microsoft Graph conditionalAccessPolicy resource type
- Entra ID Conditional Access policy evaluation logic
- Entra ID sign-in logs and Conditional Access insights
- Microsoft identity platform OAuth 2.0 and OpenID Connect docs
- NIST SP 800-63 Digital Identity Guidelines
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI