Summary

Most Zero Trust diagrams show five (or sometimes seven) pillars across the top with Conditional Access underneath, connected by arrows. What those diagrams don't show are the three factors that determine whether the design survives real‑world production: how limited the usable signal set actually is, how long an access decision remains valid, and who must build the automation the diagram quietly assumes. To keep this grounded, the article uses one recurring request as a reference point. "Quarter‑Close" is the finance engagement that runs in the final week of every quarter, and the request in question is a contract accountant on a B2B guest account accessing a labelled SharePoint site at 23:40 on the last day, from a personal laptop on a hotel network. It's an ordinary scenario — which is exactly why it reveals what the Microsoft 365 control plane can and cannot evaluate, and why the real decision surface is narrower than the pillars suggest.

The Five Pillars and the Policy Engine

Identity, endpoints (devices), network, applications and workloads, and data are not a sequence and not a maturity ladder. They are the five places a control can live. The useful part of the drawing is the convergence: all five feed one evaluator, and in Microsoft 365 that evaluator is Conditional Access. Everything else in M365 security either produces a signal for it or carries out its verdict.

A Conditional Access policy has four moving parts, and the failure modes cluster around how they combine.

PartWhat it settlesWhere people get burned
assignmentsWho and what the policy applies to, including exclusionsExclusions are permanent unless something reviews them. Every excluded group is a standing hole.
conditionsThe circumstances that must all hold for the policy to fireConditions are ANDed inside one policy. Two independent risks belong in two policies, not one condition set.
grantControlsThe requirement placed on the requestOperator is AND or OR and changes the meaning completely. A block control in any matching policy wins over every grant.
sessionControlsWhat happens after access is grantedSilently ignored by clients and resources that do not implement them, with no error to tell you.

Table 1: Describes the policy anatomy. Three of the four rows fail quietly rather than loudly.

Those combination rules matter most when several policies match the same request, which is the normal case in a tenant with any history. Figure 1 shows how three unremarkable policies resolve against a single guest request to SharePoint.

Figure 1. Conditions AND inside a policy. Policies add across the tenant. A single block ends the conversation.
Figure 1. Conditions AND inside a policy. Policies add across the tenant. A single block ends the conversation.

Strength, Not Just MFA

Requiring MFA is a coarse instrument. Authentication strengths make the requirement specific, and they are real objects you can enumerate rather than settings buried in a blade. Three ship built in with fixed IDs: ...0002 for multifactor authentication, ...0003 for passwordless MFA, and ...0004 for phishing-resistant MFA, which allows only Windows Hello for Business, FIDO2 and certificate-based multifactor. Custom strengths let you name exact method combinations, which is how you stop treating an SMS code and a security key as the same event.

Pushing the Decision Into the App

The blunt approach to protecting a sensitive site is a tenant-wide phishing-resistant requirement. It works, and it also re-authenticates several hundred people who never touch finance. Authentication contexts are the escape hatch. You define a context, reference it through applications.includeAuthenticationContextClassReferences, and bind it to a resource or a Microsoft Purview sensitivity label. The app then calls back into the engine when a session reaches for something carrying that label, so the same user in the same session is evaluated twice at two different bars. Applied to "Quarter-Close", the hard requirement lands on the moment the contractor opens a labelled document rather than on every sign-in she makes all quarter.

Before enabling any of it, run the intended request through the What-If API at POST /identity/conditionalAccess/evaluate. Give it a signInIdentity, a signInContext and as much of signInConditions as you know, and it returns every policy in the tenant with a policyApplies flag and an analysisReasons value. It is the only honest way to answer "which of our forty-one policies will fire" before you find out from a help desk queue.

What the Engine Can Actually See

The signal catalogue is finite and short enough to memorize, which is worth doing, because architecture discussions tend to assume signals that do not exist. Anything not in this list is enforced somewhere other than the token exchange, and probably later.

SignalCondition propertyFidelity and blind spots
Network locationconditions.locationsIP as the STS sees it. Behind a proxy or split tunnel that differs from what the resource sees.
Device stateconditions.devices.deviceFilterReads attributes on an existing device object. A device with no object in the directory matches no filter rule at all.
Device platformconditions.platformsSelf-reported by the client. Useful for shaping, not for assurance.
Client app typeconditions.clientAppTypesSeparates browser, modern clients, ActiveSync and other. Legacy auth lives in other and ignores MFA.
Sign-in riskconditions.signInRiskLevelsReal time and offline detections. An offline detection can raise the level after the session is already open.
User riskconditions.userRiskLevelsAccumulated, not instantaneous. It reflects history, so it lags a fresh compromise.
Insider riskconditions.insiderRiskLevelsNeeds Purview Adaptive Protection. Genuinely useful for departures and quarter-end data movement.
Authentication flowconditions.authenticationFlowsLets you block deviceCodeFlow and authentication transfer, both common in phishing today.

Table 2. Describes the whole visible surface. Data sensitivity and host posture are not on it.

Listing the signals flatters them, because it puts a self-reported platform string next to a cryptographically bound authentication method as though they were peers. Figure 2 separates them on the two axes that matter, how strongly the signal is bound to reality and how recently it was established.

Figure 2. Two signals can appear in the same policy and still deserve very different weight.

No Device Is Not Non-Compliant

This distinction causes more misconfigured tenants than anything else in the list. A grant control requiring a compliant device blocks an unmanaged machine and a non-compliant managed machine alike. A device filter cannot, because a filter is a rule evaluated against an object, and an unmanaged personal laptop has no object. Write an exclusion as device.trustType -eq "ServerAD" -and device.extensionAttribute1 -eq "FinanceKiosk" and it looks like a safety valve while providing none for the "Quarter-Close" laptop, which never enters the rule's frame of reference. The right lever there is Conditional Access App Control through Defender for Cloud Apps, session scoped, so the contractor gets browser-only access with downloads blocked instead of a binary allow or deny.

Freshness deserves the same scepticism. Intune compliance is a stored state on the device object, refreshed on check-in rather than on demand, so a machine that drifted out of compliance an hour ago can still present as compliant at token time.

Where Enforcement Really Happens

Conditional Access decides at token issuance. After that the decision lives inside a bearer token, and the resource provider honours the token, not the current state of the world. That gap between when a fact changes and when the verdict changes is the real subject of Zero Trust engineering.

Access tokens run 60 to 90 minutes by default, deliberately randomised so reauthentication does not arrive in waves. Refresh tokens are the long pole. Everything below is a way of shortening the gap, and each one interrupts something different.

LeverWhat it interruptsHow fast, and the catch
signInFrequency timeBasedRefresh token reuse after N hours or daysPredictable, blunt. Nothing happens inside the window.
signInFrequency everyTimeEvery authentication attemptSupported only for risky users, risky sign-ins and Intune enrollment. Not a general lever.
persistentBrowserThe stay-signed-in cookieBrowser sessions only. Desktop and mobile clients are unaffected.
Continuous access evaluationAn open session, mid-requestNear real time for critical events. Token lifetime extends to 28 hours in exchange.
revokeSignInSessionsRefresh tokens and browser cookiesImmediate on the refresh path. Issued access tokens survive to exp unless CAE is in play.
cloudAppSecurityActions inside the session, such as downloadRequires the reverse proxy path and a supported client.

Table 3- Describes five levers, three different clocks. Choosing one without knowing which clock it runs on is the usual error.

Put those levers on a shared timeline and the argument stops being abstract. Figure 3 assumes a token issued at minute zero and something going wrong at minute twelve.

Figure 3. Two of the four levers do nothing at all about the token already in the client's hands.
Figure 3. Two of the four levers do nothing at all about the token already in the client's hands.

The Session in the Token

The claims tell you what the engine concluded and, more usefully, what it did not.

Figure 4 - Shows the session token results
Figure 4 - Shows the session token results

amr uses the RFC 8176 method references and says multifactor happened. It does not say which factor, which is the entire reason authentication strengths exist. acrs carrying c3 is the durable proof that the step-up for the finance context was satisfied, and it is the claim an app should check rather than trusting amr. The token is an RFC 9068 JWT access token presented under the RFC 6750 bearer scheme.

Cutting a Session Short

Revocation is less magical than it sounds. Calling revokeSignInSessions resets signInSessionsValidFromDateTime on the user object, and every refresh token and session cookie issued before that timestamp stops working. Access tokens already in flight are untouched, which is the whole reason continuous access evaluation exists.

CAE only applies to clients that opted in by declaring the cp1 capability. When a critical event lands, such as the account being disabled, the password changing, sessions being revoked, or Entra ID Protection raising the user to high risk, the resource rejects the presented token in-session with a 401 carrying error="insufficient_claims" and a base64url claims parameter naming the nbf the next token must beat. That is the RFC 9470 step-up pattern, and the client is expected to go back for a new token, at which point every condition is evaluated again.

Two caveats matter in practice. Location change is normally enforced on a coarse basis, and when the IP the STS sees does not match the IP the resource sees, common on split tunnels and proxies, Entra ID issues a one hour token and stops enforcing location change for that hour. Strict location enforcement, currently in preview, removes that grace period for tenants with stable egress. A hotel network is exactly where the mismatch shows up, so "Quarter-Close" is a good stress test for whether your egress assumptions hold.

Visibility and Automation

The right-hand column of the reference architecture is drawn as a foundation. Defender XDR correlates, Sentinel stores and orchestrates, Security Copilot summarises, and none of it produces an outcome until somebody writes the query and the action.

Figure 5 - The dashed segment is the one nobody budgets for and the only one that closes the loop.
Figure 5 - The dashed segment is the one nobody budgets for and the only one that closes the loop.

Watching Policy Before Trusting It

Report-only mode is the single most underused feature in the stack. A policy in enabledForReportingButNotEnforced evaluates against live traffic and records what it would have done, surfacing in the sign-in logs as reportOnlyFailure and reportOnlyInterrupted rather than as a blocked user. In Sentinel the results hide one level down, inside the ConditionalAccessPolicies array on each SigninLogs row, so you need an mv-expand before you can group by policy name and outcome.

Run that for a full quarter before enforcing, not a week. Month-end and quarter-close traffic arrives from hotels, airports and personal devices that a seven day baseline never sees, and a policy validated in the second week of a quarter will find the "Quarter-Close" population for the first time on the night it can least afford to.

Endpoints and Permissions

Everything discussed above is reachable from Microsoft Graph, and the least-privileged permission is usually narrower than the one people reach for. Scope playbook identities to this list rather than to Directory.ReadWrite.All.

OperationEndpointLeast-privileged permission
Create or update a policy

POST

/identity/conditionalAccess/policies

Policy.ReadWrite.

ConditionalAccess

Test a request against policy

POST

/identity/conditionalAccess/evaluate

Policy.Read.

ConditionalAccess

List authentication strengths

GET

/policies/authenticationStrengthPolicies

Policy.Read.

AuthenticationMethod

Revoke refresh tokens and cookies

POST

/users/{id}/revokeSignInSessions

User.RevokeSessions.All
Confirm a user is compromised

POST

/identityProtection/riskyUsers/

confirmCompromised

IdentityRiskyUser.

ReadWrite.All

Run a Defender hunting query

POST

/security/runHuntingQuery

ThreatHunting.Read.All

Table 4 - Describes the confirmCompromised call is the one that closes the loop, because it raises user risk and every risk-based policy starts applying at once.

Conclusion

The five pillars tell you where a control can live, and Conditional Access is the only place a decision gets made. Whether the architecture holds for a request like "Quarter-Close" does not turn on the quality of that decision. It turns on how long the decision stays valid after the facts change, and that number is one you set on purpose or inherit by accident.