A 30-second Conditional Access read and the four gaps it usually surfaces
Four Conditional Access controls decide most of a Microsoft 365 tenant identity posture: admin MFA, legacy-auth block, MFA for all, and a device gate. Here is the read-only check that scores them and what each gap means.
A Conditional Access review does not need a week. Four controls carry most of the identity posture, and each one is a yes or no that Microsoft Graph answers directly. The ITSailor tenant scan reads the enabled policies and derives these four, then Copilot identity readiness on top. This is the same logic, described so you can run the read yourself.
Last verified: 2026-08-03.
Prerequisites
- Microsoft Graph access with Policy.Read.All.
- Global Reader or an equivalent read-only directory role is enough. No write access is needed.
The four controls, and what a gap means
- MFA for admins. An enabled policy that targets directory roles or all users and grants on MFA. Missing this is a high gap: registering MFA is not the same as enforcing it at sign-in.
- Legacy authentication blocked. An enabled policy that blocks Exchange ActiveSync and "other" clients. Legacy protocols bypass MFA and are the most common takeover vector, so a missing block is a high gap.
- MFA for all users. A policy targeting all users with MFA. Missing this is a medium gap because coverage may be partial.
- Device compliance gate. A policy requiring a compliant or hybrid-joined device. Missing this is a low gap and a candidate for sensitive apps.
If the tenant has no Conditional Access policies at all, that is a single high finding on its own: identity then rests on whatever Security Defaults provide.
Expected output
# Read enabled Conditional Access policies and their grant controls
Connect-MgGraph -Scopes "Policy.Read.All"
Get-MgIdentityConditionalAccessPolicy -All |
Where-Object { $_.State -eq 'enabled' } |
Select-Object DisplayName,
@{n='Grants';e={ $_.GrantControls.BuiltInControls -join ',' }},
@{n='ClientApps';e={ $_.Conditions.ClientAppTypes -join ',' }}
Read the output against the four controls above. The paid tenant scan does this automatically and adds a Copilot identity-readiness score at /tools/saas-auditor.
Side effects
This is a read. It changes nothing. The only cost is the Graph call and the reviewer time.
Rollback
No rollback applies to a read-only check. When you act on a gap, stage the new policy in report-only mode first, confirm it in sign-in logs, then enable it, so the enforcement itself has a rollback.
Limitations
This scores four controls, not every Conditional Access nuance such as named locations, risk-based policies, or session controls. Copilot identity readiness is only the identity side. The standing gate before any Copilot rollout is a data-oversharing and DLP review, which the score always flags and which this read does not perform.
Tenant Monitor runs a version of this same four-control read every month and keeps each result as a dated artifact instead of a one-time score, from Tenant Monitor.
Sources and further reading
Turn the procedure into a tenant decision.
The Architecture Workshop maps the checks, side effects, and rollback path to your own Microsoft 365 environment.
Review the workshopMore from Ops Log
A CSP partner may not sell to itself or an affiliate: Microsoft names two own-use routes instead
Microsoft's CSP documentation says partners are barred by contract from selling Microsoft or third-party offers to themselves or an affiliate as end customer. The same page names two own-use routes: a Shared Services tenant for Azure, or a separate tenant bought through another CSP partner.
A Microsoft 365 DKIM CNAME target has two documented formats: read it per domain, never build it
Microsoft documents two DKIM CNAME target formats for Microsoft 365 custom domains, one ending in onmicrosoft.com and one in dkim.mail.microsoft. The page splits them by new versus existing custom domain and never mentions tenant age. Read each domain's values with Get-DkimSigningConfig.
The Graph site permissions endpoint lists application grants: Sites.FullControl.All buys an empty array
GET /sites/{siteId}/permissions lists the grants held by applications on a site, not the people who can open it, and returns an empty array on any tenant that never used Sites.Selected. Microsoft documents Sites.FullControl.All as its least privileged permission. Two cheaper reads answer the real question.