In this section
MSA3.1 Conditional Access as the Policy Engine
Module 2 designed what your authentication architecture requires: phishing-resistant methods for admins, passkeys for the workforce, legacy auth blocked, SSPR hardened, tokens protected. But requirements without enforcement are suggestions. A policy that says "admins must use FIDO2 keys" has no effect unless something evaluates every admin sign-in and blocks the ones that don't use a FIDO2 key. That something is Conditional Access: the central policy engine for every access decision in your M365 tenant. This sub teaches how CA works as a system: the evaluation pipeline, the signals it consumes, the controls it enforces, the interactions between policies, and the blind spots where CA evaluation doesn't occur. You need to understand the engine before you design the policies.
Most administrators interact with CA through the portal, click "New policy," select users, select apps, pick a grant control. The policy "works." But they don't understand the evaluation flow, so they can't predict what happens when two policies conflict, when a sign-in matches multiple policies, when a session control interacts with a grant control, or when CA evaluation doesn't occur at all. Unpredictable CA behavior is the primary cause of lockouts, bypasses, and "why is this user blocked?" helpdesk tickets. This sub teaches CA as an engineering system: the inputs, the processing, the outputs, the edge cases, and the failure modes, so that when you design policies in MSA3.2–3.13, you can predict exactly what will happen for every sign-in scenario.
Estimated time: 50 minutes.
Where CA sits in the authentication pipeline
CA evaluates after first-factor authentication completes, not before, not during. The user has already entered their username and presented their first authentication factor (password, passwordless method, or certificate) before CA sees the sign-in. This positioning is fundamental to understanding what CA can and can't do.
USER ACTION PIPELINE STAGE CA INVOLVEMENT
────────────────────────── ───────────────────────── ──────────────
User enters username Realm discovery None. CA not invoked
(which IdP handles auth?)
User presents password First-factor authentication None, credential validation
or passwordless method (credential validation) happens before CA evaluates
▼ FIRST FACTOR SUCCEEDS ▼
Session signals collected CA PHASE 1 YES, signals gathered:
(signal collection + user, device, location,
policy matching) app, risk, client type
Matching policies identified CA PHASE 1 continues YES: all enabled and
report-only policies checked
Grant controls enforced CA PHASE 2 YES. MFA prompt, device
(user prompted for MFA, (control enforcement) compliance check, or block
device compliance check)
Session controls applied CA PHASE 2 continues YES, sign-in frequency,
persistent browser, CAE mode,
Token Protection
Token issued Post-CA No. CA has finished.
Token validity governed by
token lifetime + CAE.Two critical implications of this positioning:
CA is not a frontline defense against brute-force or password spray. Those attacks hit the authentication endpoint directly: the credential validation stage. If the password is wrong, authentication fails before CA is involved. Smart lockout and Identity Protection handle pre-CA threats. CA only evaluates sign-ins where the first factor succeeded, meaning the attacker has valid credentials.
CA doesn't evaluate resource access after token issuance. Once the access token is issued, CA's job is done. If the token is stolen and replayed, CA doesn't re-evaluate the replay (except through CAE for supported apps and critical events). Post-authentication protection comes from the session controls CA applied (CAE, Token Protection, sign-in frequency) and from the resource providers that participate in CAE. This is why MSA2.9's token protection design and MSA3.1's CA evaluation are complementary layers, not redundant ones.
Phase 1. Signal collection and policy matching
When first-factor authentication completes, Entra ID collects the session signals:
SIGNAL SOURCE EXAMPLE VALUE
User identity Authentication result user@contoso.com, group memberships
Application Token request Exchange Online (app ID: 00000002...)
Device platform User agent / device registration Windows 11, iOS 18, macOS 15
Device state Intune / Entra registration Compliant, Hybrid joined, Unmanaged
Location IP address → named location Corporate network, Untrusted, Specific country
Client app type Authentication protocol Browser, Mobile app, Legacy client
Sign-in risk Identity Protection None, Low, Medium, High
User risk Identity Protection None, Low, Medium, High
Authentication flow Protocol used Browser, Device code, Auth transferEntra ID evaluates all enabled CA policies (and all report-only policies) against these signals. A policy "matches" when all its conditions are satisfied. If a user is in the included group, accessing the included app, from a location that isn't excluded, on a platform that isn't excluded: the policy matches.
Multiple policies can match a single sign-in. If Policy A requires MFA for all users accessing all apps, and Policy B requires device compliance for all users accessing Exchange Online, a user signing into Exchange Online matches both policies. Both sets of controls apply.
Phase 2. Control enforcement
After all matching policies are identified, Entra ID enforces the combined controls:
Block takes priority. If any matching policy has a "Block" grant control, access is denied. It doesn't matter if another matching policy grants access with MFA, block wins. This is why block policies must be precisely targeted. A poorly scoped block policy can override all your grant policies.
Grant controls are additive (AND by default within a policy). If a single policy requires "Require MFA" AND "Require compliant device," the user must satisfy both. Between policies, the controls accumulate, if Policy A requires MFA and Policy B requires device compliance, the user must satisfy both (MFA from Policy A, compliance from Policy B).
Session controls apply after grant controls are satisfied. If the user passes all grant controls, session controls modify the resulting session: sign-in frequency (how often to re-authenticate), persistent browser (whether the session survives browser close), CAE mode (opportunistic vs strict), and Token Protection (device binding).
Query your existing policies to see this in practice:
Connect-MgGraph -Scopes "Policy.Read.All"
$policies = Invoke-MgGraphRequest -Method GET `
-Uri "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" `
-OutputType PSObject
Write-Host "=== CA POLICY INVENTORY ==="
Write-Host "Total policies: $($policies.value.Count)"
Write-Host ""
$enabled = 0; $reportOnly = 0; $disabled = 0
foreach ($p in $policies.value) {
switch ($p.state) {
"enabled" { $enabled++ }
"enabledForReportingButNotEnforced" { $reportOnly++ }
"disabled" { $disabled++ }
}
}
Write-Host "Enabled: $enabled"
Write-Host "Report-only: $reportOnly"
Write-Host "Disabled: $disabled"
Write-Host ""
foreach ($p in $policies.value | Sort-Object displayName) {
# Determine grant type
$grant = if ($p.grantControls.builtInControls -contains 'block') {
"BLOCK"
} elseif ($p.grantControls.authenticationStrength) {
"Auth Strength"
} elseif ($p.grantControls.builtInControls -contains 'mfa') {
"Require MFA"
} elseif ($p.grantControls.builtInControls -contains 'compliantDevice') {
"Compliant Device"
} else {
($p.grantControls.builtInControls -join "+")
}
# Determine session controls
$sessions = @()
if ($p.sessionControls.signInFrequency.isEnabled) {
$sessions += "SIF:$($p.sessionControls.signInFrequency.value)$($p.sessionControls.signInFrequency.type[0])"
}
if ($p.sessionControls.continuousAccessEvaluation.mode -eq 'strictEnforcement') {
$sessions += "CAE:strict"
}
if ($p.sessionControls.signInTokenProtection) {
$sessions += "TokenProtect"
}
if ($p.sessionControls.persistentBrowser.isEnabled) {
$sessions += "PersistBrowser:$($p.sessionControls.persistentBrowser.mode)"
}
$sessionStr = if ($sessions.Count -gt 0) { $sessions -join ", " } else { "—" }
# User scope
$userScope = if ($p.conditions.users.includeUsers -contains "All") { "All users" }
elseif ($p.conditions.users.includeGroups) { "$($p.conditions.users.includeGroups.Count) group(s)" }
elseif ($p.conditions.users.includeRoles) { "$($p.conditions.users.includeRoles.Count) role(s)" }
else { "Specific" }
# App scope
$appScope = if ($p.conditions.applications.includeApplications -contains "All") { "All apps" }
else { "$($p.conditions.applications.includeApplications.Count) app(s)" }
$stateIcon = switch ($p.state) {
"enabled" { "●" }
"enabledForReportingButNotEnforced" { "◐" }
"disabled" { "○" }
}
Write-Host "$stateIcon $($p.displayName)"
Write-Host " Users: $userScope | Apps: $appScope | Grant: $grant | Session: $sessionStr"
}Reading your results:
If you completed MSA2.11's lab, you'll see the two CA policies you created: CA-Admins-RequirePhishResistMFA-AdminPortals (report-only, Auth Strength) and CA-All-BlockLegacyAuth-AllApps (report-only, BLOCK). Note the state indicator: ● enabled, ◐ report-only, ○ disabled. Report-only policies are evaluated but don't enforce, they log what would have happened. Disabled policies are not evaluated at all.
If your tenant has additional policies (from Security Defaults migration or previous configuration), the inventory shows their grant types and session controls. Policies using "Require MFA" (the legacy grant control) should be migrated to "Auth Strength" per MSA2.7's migration plan.
Entra Admin Center
Protection → Conditional Access → Policies
The list shows all policies with name, state (On/Off/Report-only), and last modified date. Click any policy to see its full configuration: assignments (users, apps, conditions) and controls (grant, session).
View evaluation results:
Protection → Conditional Access → Insights and reporting
Shows policy evaluation outcomes over time: how many sign-ins each policy applied to, how many succeeded, failed, or were report-only. This is your primary tool for understanding policy impact before enforcement.
The six signals CA evaluates
Each signal is a dimension in the access decision. The more signals a policy uses, the more specific its targeting, and the harder it is for an attacker to satisfy all conditions.
User or group membership, who is signing in. CA can target all users, specific groups, specific directory roles, or specific users. Exclusions are applied after inclusions. A user who matches both an include group and an exclude group is excluded (exclusion wins). Guest users can be targeted specifically through the "Guest or external users" condition, which distinguishes between B2B collaboration guests, B2B direct connect users, local guest users, and service provider users.
Application or user action, what the user is accessing. CA targets applications by their app registration ID. Common targets: all cloud apps, specific apps (Exchange Online 00000002-0000-0ff1-ce00-000000000000, SharePoint Online 00000003-0000-0ff1-ce00-000000000000, Azure portal 797f4846-ba00-4fd7-ba43-dac1f8f63013), or user actions (register security information, register/join devices). The "All resources" target now includes apps that request only minimal OIDC scopes: a change that completed rollout in June 2026, closing a previous enforcement gap where some sign-ins bypassed CA because the app requested only basic profile scopes.
Device platform and state: the device the user is on. Platform detection uses the user agent string (unverified, can be spoofed by the client). Device state is verified through Entra registration: compliant (Intune-managed and meeting all compliance policies), hybrid joined (synced from on-premises AD via Entra Connect), or Entra registered (user-registered but not necessarily managed). Unmanaged devices have no verified device state. CA can only evaluate the unverified platform claim.
Network location, where the sign-in originates. Named locations map IP ranges or countries/regions to labels (corporate office, trusted partner, blocked country). The compliant network location (via Global Secure Access) verifies the traffic path, not just the source IP. Standard IP-based location is spoofable (VPN, proxy). MSA3.7 covers the limitations in detail.
Sign-in risk and user risk, real-time threat intelligence from Identity Protection. Sign-in risk evaluates the current authentication attempt (unfamiliar IP, impossible travel, anomalous token, suspicious browser fingerprint). User risk evaluates the account's overall risk level based on accumulated signals (leaked credentials detected in dark web monitoring, anomalous activity patterns over time). Risk-based policies are the most powerful CA signal because they adapt in real time: a sign-in that was low-risk yesterday can be high-risk today because Identity Protection detected the user's credentials in a new breach dump. Requires Entra ID P2.
Client app type and authentication flow: the protocol and flow used for authentication. CA distinguishes between browsers, mobile apps/desktop clients, Exchange ActiveSync, and other legacy clients. The authentication flow condition (GA) distinguishes device code flow and authentication transfer from standard flows, critical for blocking device code phishing attacks where an attacker tricks a user into entering a device code on a legitimate Microsoft sign-in page.
Query a recent sign-in to see exactly which signals CA evaluated:
# Get a recent sign-in with CA evaluation details
$signIn = (Invoke-MgGraphRequest -Method GET `
-Uri "https://graph.microsoft.com/v1.0/auditLogs/signIns?`$top=1&`$orderby=createdDateTime desc" `
-OutputType PSObject).value[0]
Write-Host "=== CA SIGNALS FOR SIGN-IN ==="
Write-Host "User: $($signIn.userPrincipalName)"
Write-Host "App: $($signIn.appDisplayName) ($($signIn.appId))"
Write-Host "Platform: $($signIn.deviceDetail.operatingSystem) / $($signIn.deviceDetail.browser)"
Write-Host "Device: $(if ($signIn.deviceDetail.deviceId) { 'Registered' } else { 'Unregistered' })"
Write-Host "IP: $($signIn.ipAddress)"
Write-Host "Location: $($signIn.location.city), $($signIn.location.countryOrRegion)"
Write-Host "Risk (sign-in): $($signIn.riskLevelDuringSignIn)"
Write-Host "Risk (user): $($signIn.riskLevelAggregated)"
Write-Host "Client app: $($signIn.clientAppUsed)"
Write-Host ""
Write-Host "CA policies evaluated:"
foreach ($ca in $signIn.conditionalAccessPolicies) {
$icon = switch ($ca.result) {
"success" { "✓ Applied" }
"failure" { "✗ Blocked" }
"notApplied" { "○ Not matched" }
"reportOnlySuccess" { "◐ Report-only (would pass)" }
"reportOnlyFailure" { "◑ Report-only (would block)" }
default { $ca.result }
}
Write-Host " $icon — $($ca.displayName)"
}Reading your results: The sign-in log shows every signal CA received and every policy it evaluated. The conditionalAccessPolicies array lists every policy (enabled and report-only) with the evaluation result. notApplied means the policy's conditions didn't match this sign-in (the user wasn't in the target group, or the app wasn't targeted). success means the policy matched and the user satisfied the controls. failure means the policy matched but the user couldn't satisfy the controls: the sign-in was blocked. Report-only results show what would have happened if the policy were enforced.
What CA can't see: the blind spots
Understanding the blind spots is as important as understanding the signals. Every blind spot is a potential bypass path that an attacker can exploit.
Client credentials flow (workload identities). User-targeted CA policies don't evaluate service principal authentication via the client credentials flow. The POST to the token endpoint with a client ID and secret never enters the user sign-in pipeline. MSA2.6 covered this in detail: the fix is Workload ID Premium ($3/workload/month) which enables separate CA policies targeting service principals. Without it, your entire workload identity population authenticates outside CA regardless of how comprehensive your user-facing policies are.
Legacy authentication (if not blocked). Legacy protocols (SMTP AUTH, POP3, IMAP4, Exchange ActiveSync with basic auth) are client app types that CA can target, but only if you've created a block policy. The legacy auth block from MSA2.5 and MSA3.3 Baseline 1 must be enforced before any other policy matters. Without it, an attacker with valid credentials authenticates via a legacy protocol that never triggers the MFA requirement from your other policies.
User agent spoofing (device platform). The device platform condition relies on the user agent string, which the client controls. An attacker can set their browser's user agent to "iPhone" when accessing from a Windows machine, or vice versa. Don't use platform-based policies as security controls (e.g., "block all Linux sign-ins to prevent attackers"): the attacker spoofs the user agent and the policy doesn't match. Use platform conditions for user experience (different session settings per platform) and use device compliance or registration (verified by Entra, not self-reported) for security controls.
OIDC minimal-scope sign-ins (closing in 2026). Until recently, applications requesting only basic OIDC scopes (openid, profile, email, offline_access) or a small set of directory scopes could bypass CA policies that targeted "All resources" if those policies had resource exclusions. This created a gap where some sign-ins weren't evaluated. Microsoft closed this gap with an enforcement change that completed rollout by June 2026. CA policies targeting "All resources" now evaluate these sign-ins even with resource exclusions. If you have custom or legacy apps that previously relied on this exemption, they may now receive CA challenges they didn't before. Test with the What If tool if you deploy resource exclusions.
Token replay after authentication. CA evaluates the sign-in event. Once a token is issued, CA doesn't re-evaluate individual resource access requests (except through CAE for supported apps and critical events). A stolen access token used from a different IP may not trigger CA re-evaluation for non-CAE applications. The compensating controls: CAE (near-real-time revocation for supported apps), Token Protection (device binding for native Windows apps), sign-in frequency (forces periodic re-authentication), and compliant network (verifies network path on every access for GSA-connected tenants).
Offline and cached data. Applications that cache data locally. Outlook's offline cache, OneDrive sync client, Teams offline messages, continue to provide access to previously downloaded content even when CA would block a new sign-in. CA controls the authentication, not the data that was already synchronized. If a user's account is disabled, their Outlook may still show cached emails until the cached tokens expire and the client attempts to refresh. The practical exposure window depends on the app's cache retention and the token lifetime (shortened by CAE and sign-in frequency).
How policies interact: the resolution model
When multiple policies match a single sign-in, CA combines their controls:
SCENARIO RESULT
-------------------------------------- ------------------------------------
Policy A: Grant with MFA User must satisfy MFA + compliance.
Policy B: Grant with compliant device Both controls apply (additive).
Policy A: Grant with MFA User is BLOCKED. Block overrides
Policy B: Block any grant from other policies.
Policy A (report-only): Block User is NOT blocked (policy is
report-only). Log shows what would
have happened.
Policy A: Grant with Auth Strength User must satisfy auth strength +
(phishing-resistant) compliant device. Auth strength
Policy B: Grant with compliant device specifies WHICH MFA satisfies the
grant: the strongest requirement
from all matching policies applies.
No policies match Access is granted with no additional
controls. CA doesn't have a default
deny, it has a default allow when
no policy matches. This is the most
dangerous behavior if your policies
have coverage gaps.The default-allow model is the most critical design implication. If a sign-in doesn't match any CA policy, access is granted with no additional controls. This means coverage gaps in your policy framework are silent, there's no alert when a sign-in bypasses CA because no policy matched. The only defense against coverage gaps is comprehensive policy design (MSA3.2) with a baseline policy targeting all users and all apps (MSA3.3).
# Check for coverage gaps: users not covered by any CA policy
# This is an approximation, full gap analysis requires the What If tool
$allUsersPolicy = $policies.value | Where-Object {
$_.state -eq "enabled" -and
$_.conditions.users.includeUsers -contains "All" -and
$_.conditions.applications.includeApplications -contains "All"
}
Write-Host "=== COVERAGE GAP CHECK ==="
Write-Host "Policies targeting All users + All apps (enabled): $($allUsersPolicy.Count)"
if ($allUsersPolicy.Count -eq 0) {
Write-Host "⚠ No policy covers all users + all apps."
Write-Host " Any sign-in that doesn't match a specific policy gets"
Write-Host " NO CA evaluation, default allow with no controls."
Write-Host " This is the coverage gap MSA3.3's baseline policy closes."
} else {
foreach ($p in $allUsersPolicy) {
Write-Host " ✓ $($p.displayName)"
}
}Anti-Pattern
The tenant has 15 CA policies, each targeting a specific group or application. No policy targets "All users + All apps." The admin team believes their policies cover everyone. But 47 users aren't in any of the targeted groups, they authenticate with password only, no MFA, no CA evaluation. The coverage gap is invisible because CA doesn't alert on sign-ins that match zero policies. The fix: a baseline policy (MSA3.3) targeting all users + all apps + requiring at minimum standard MFA. Every sign-in hits at least this policy. Specific policies add stronger requirements on top.
Before moving on, verify your understanding: Run the CA policy inventory against your tenant. How many policies exist? How many are enabled, report-only, and disabled? Which grant types are used (Block, Auth Strength, Require MFA, Compliant Device)? Explain CA's default-allow model. What happens when a sign-in doesn't match any CA policy? Why is this more dangerous than a default-deny model? What is the architectural fix?
Reusable script: the commands from this sub assembled for operational use:
Save the inventory output to 03-conditional-access/ca-policy-inventory.md. This is the starting point for Module 3, you need to know what exists before designing what should exist. Include: policy count by state, grant type distribution, session control coverage, and the coverage gap assessment.