Conditional Access needs Microsoft Entra ID P1, included for example in Microsoft 365 Business Premium and E3. Security defaults have to be switched off to use it, so plan for your baseline policies to be active on the same day you turn security defaults off. The sequence below is the one I use: first the protection against lockout, then evaluation without effect, then enforcement in rings.
Phase 1: break-glass accounts that actually work
Before the first policy exists, the tenant needs at least two emergency access accounts. They are the way back when a policy, an MFA outage or a federation problem locks out every other administrator.
- Cloud-only: accounts on the
onmicrosoft.comdomain, not synchronised, not tied to a person. - Global Administrator, permanently assigned: no just-in-time activation that could itself fail in an emergency.
- Strong sign-in: Microsoft now requires MFA for the admin portals, emergency accounts included. FIDO2 security keys kept separately, for example in safes at two locations, work well.
- Excluded from every policy: through a dedicated group that appears under "Exclude" in each policy, including Microsoft-managed policies that appear later.
- Monitored: every sign-in raises an alert, through sign-in logs sent to a Log Analytics workspace and an alert rule in Azure Monitor, or through Microsoft Sentinel.
Excluded is not the same as usable. I have seen an emergency account that sat in the exclusion group and was excluded from every policy, but had neither a directory role nor a second factor. It would have signed in during an emergency and been unable to fix anything. Test the accounts on a schedule, for example quarterly, and check that the alert fires.
Phase 2: report-only, sign-in logs and What If
Every new policy starts in report-only mode. Entra ID evaluates it at every sign-in but does not enforce it. In the sign-in logs, the "Report-only" tab shows for each sign-in whether the policy would have applied, and with what result.
- Sign-in logs: look for sign-ins where a report-only policy would have failed. Those are the people and applications that will have problems after enforcement.
- Insights workbook: "Conditional Access insights and reporting" summarises the impact over time. It needs the logs in a Log Analytics workspace.
- What If: simulate one specific sign-in (user, application, platform, location) and see which policies apply. Ideal for checking emergency and service accounts before enforcement.
The same check in PowerShell, with the Microsoft Graph SDK:
Connect-MgGraph -Scopes "AuditLog.Read.All","Policy.Read.All"
$since = (Get-Date).ToUniversalTime().AddDays(-7).ToString("yyyy-MM-ddTHH:mm:ssZ")
Get-MgAuditLogSignIn -Filter "createdDateTime ge $since" -All |
ForEach-Object {
$s = $_
$s.AppliedConditionalAccessPolicies |
Where-Object { $_.Result -eq "reportOnlyFailure" } |
ForEach-Object { [pscustomobject]@{ User = $s.UserPrincipalName; App = $s.AppDisplayName; Client = $s.ClientAppUsed; Policy = $_.DisplayName } }
} | Group-Object Policy, User | Sort-Object Count -Descending | Select-Object Count, Name
Let report-only run for at least one to two weeks so rare cases show up: month-end closing, field staff, holiday cover, scripts that only run at night. On large tenants, narrow the filter or query the Log Analytics workspace instead.
Phase 3: rings, from administrators to everyone
- Administrators: privileged roles first. The strictest rules apply to them anyway, and they can fix problems themselves. Target directory roles, not just a group, so new administrators are covered automatically.
- Pilot group: IT plus a few people from the business, with different devices and ways of working. Included explicitly, everyone else stays out.
- All users: only when the pilot runs without open issues, with the defined exclusions.
Exclusions are necessary, but every exclusion is a gap. Manage them through groups, document the reason and review membership. A service account excluded "temporarily" is usually still there two years later. Where possible, replace interactive service accounts with app registrations and certificates. Workload identities are not subject to user policies.
The building blocks
Legacy authentication
Legacy protocols cannot do MFA. Blocking the client apps "Exchange ActiveSync clients" and "Other clients" for all users belongs in the baseline. Before that, filter the sign-in logs by client app to see who still uses them. Typical candidates are multifunction devices and applications that send with SMTP AUTH.
Authentication strengths
"Require MFA" accepts any registered method, SMS included. The built-in strength "Phishing-resistant MFA" only accepts Windows Hello for Business, FIDO2 security keys and passkeys, and certificate-based authentication. Make it the standard for administrators. For everyone else, encourage passkey registration first, then require the strength.
Device-based conditions
"Require compliant device" and "Require Microsoft Entra hybrid joined device" are strong, but only once Intune compliance or hybrid join demonstrably works. Devices without an assigned compliance policy should not count as compliant. In the browser, the device state has to reach Entra ID: Edge with a signed-in profile on Windows, an extension for Chrome. On iOS and Android, a broker app is needed.
Guests
Guests are often forgotten or excluded wholesale. Both are wrong. Target them with the guest and external user condition, and decide per partner organisation in cross-tenant access settings whether you trust their MFA and device state. Device-based policies only work for guests if you trust the home tenant's device state, otherwise you lock out every guest. Test with a real guest account, not only in What If.
Document every policy, and keep a way back
- Name: number, target, resource and effect, for example
CA010-Admins-AllApps-PhishingResistantMFA. - Owner and purpose: one person who approves exclusions, and one sentence on the risk the policy addresses.
- Rollback: back to report-only, not delete.
# Export every policy as JSON before a change
Connect-MgGraph -Scopes "Policy.Read.All"
$dir = New-Item -ItemType Directory -Force -Path ".\ca-export-$(Get-Date -Format yyyyMMdd-HHmm)"
Get-MgIdentityConditionalAccessPolicy -All | ForEach-Object {
$_ | ConvertTo-Json -Depth 20 | Out-File (Join-Path $dir "$($_.Id).json") -Encoding utf8
}
# Rollback for one policy: back to report-only
Connect-MgGraph -Scopes "Policy.Read.All","Policy.ReadWrite.ConditionalAccess"
Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId "<policy id>" -State "enabledForReportingButNotEnforced"
Files are named by policy ID, because display names can contain characters a file system does not accept. The Entra audit logs show who changed a policy and when. Add the why in your change record.
Checklist
- Two cloud-only emergency accounts with FIDO2 keys, Global Administrator, in an exclusion group
- Alert on every emergency sign-in, tested regularly
- Sign-in logs in a Log Analytics workspace with sufficient retention
- Every new policy in report-only first, evaluated with sign-in logs and What If
- Rollout in rings: administrators, pilot, all users
- Legacy authentication blocked, remaining users identified first
- Device-based policies only after compliance or hybrid join is proven
- Phishing-resistant authentication strength at least for administrators
- Guests handled deliberately, trust settings reviewed per partner
- Every policy named, owned, documented, exported before changes