Conditional Access Resource Exclusions Remediation Guide with SIEM detections
- Rory Wade
- 23 hours ago
- 13 min read
Updated: 19 hours ago
You've likely heard that a change to resource exclusions in Conditional Access is coming, starting from the 27th of March 2026. Microsoft published MC1223829 through the M365 Message Center and followed up with an Entra Blog post in January. But what does this really mean, and how can you identify the potential impact to your organisation?
In this article we'll break the problem down through three steps. At each step you'll be able to determine whether you should care or whether you're in the clear. By the third step you'll be running scripts and trawling through your SIEM to understand the absolute blast radius. For most organisations, it shouldn't be too large.
I'm writing this article because I have some personal context on the problem. Over the past year I reported a vulnerability (VULN-154544) to Microsoft through the MSRC process that was classified as important, and it was one of likely a few that contributed to this enforcement change. Since then, through our identity security work at Modern 42 across government, financial services, and enterprise clients, we've been tracking how this enforcement gap manifests in production environments. That fieldwork is what this article draws on.
Step 1: Do You Even Have Conditional Access Resource Exclusions?
Microsoft's starting point is simple and reasonable: do you have application exclusions in any of your Conditional Access policies targeting All resources (formerly "All cloud apps")?
To check, navigate to:
Entra admin centre → Protection → Conditional Access → Policies

Open each policy and look at Target resources → Include. If a policy targets All resources and then defines one or more entries under the Exclude tab, you're in scope.
If none of your policies have resource exclusions, your job is done. Go get a coffee.
If you received the MC1223829 notification in your M365 Message Center, Microsoft's telemetry has already confirmed that at least one such policy exists in your tenant.
Step 2: Understanding the Problem
If you're still reading, you have at least one policy with resource exclusions. Let's unpack what that actually means for your end user's authentications.
How Conditional Access Evaluates Token Requests
When a user authenticates, they tell Microsoft two things (very much a simplification):
Which app they're authenticating from, known as the client (e.g. Microsoft Teams, a custom LOB app, Azure CLI)
What they're trying to access, known as the resource or the audience for the access token (e.g. Exchange Online, SharePoint, Microsoft Graph)
This is an important distinction. Conditional Access policies don't primarily care which app you're authenticating from. They care about who is the audience for the access token you're requesting. The resource, not the client, is what CA policies evaluate against.
The Bypass: Low-Privilege Scopes
This is where the enforcement gap lives. Today, if you have a CA policy targeting All resources with one or more resource exclusions, certain low-privilege scope requests are exempt from policy enforcement entirely. These exemptions were introduced to prevent inadvertently blocking basic authentication flows, but they opened a hole that has persisted for years.
The affected scopes differ depending on the client type:
Native clients and Single Page Applications (SPAs):
API | Exempt Scopes |
Azure AD Graph | email, offline_access, openid, profile, User.Read |
Microsoft Graph | email, offline_access, openid, profile, User.Read, People.Read |
Confidential clients (if excluded from an All resources policy):
API | Exempt Scopes |
Azure AD Graph | |
Microsoft Graph | email, offline_access, openid, profile, User.Read, User.Read.All, User.ReadBasic.All, People.Read, People.Read.All, GroupMember.Read.All, Member.Read.Hidden |
What This Means in Practice
When you exclude an application like Jamf, or a legacy HR system, from your MFA and device compliance policy, you're not just letting that application bypass enforcement. You're letting every service principal bypass the policy so long as it's only requesting the low-privilege scopes listed above against Azure AD Graph or Microsoft Graph.
The only other layer of protection is which service principals have been granted these low-privilege scopes via their API Permissions configuration. In practice, this is weak protection. As anyone who has audited a production tenant knows, User.Read on Microsoft Graph is almost universally granted.
In some cases this might be behaviour you didn't even know about. If your directory data is sensitive, it's worth noting that MFA and device compliance were likely not being enforced when reading basic profile and group membership data from a non-corporate device or network. Most organisations don't care about this level of access, but those in government and financial services absolutely do.
In our work across regulated industries we've consistently found that this gap is wider than people expect. Large organisations with mature CA policies often have several vendor-managed applications that have been silently benefiting from this quirk, sometimes with thousands of daily sign-ins. These apps won't necessarily break after March 27th, but end-user experience may take a noticeable hit if MFA flows aren't already seamless.
After March 27th
Once enforcement begins, these low-privilege scope requests will be mapped to the Azure AD Graph (Windows Azure Active Directory, 00000002-0000-0000-c000-000000000000) resource for Conditional Access evaluation. Your CA policies targeting All resources will now enforce against these flows, even when resource exclusions are present.
The rollout is phased from March 27 through June 2026 across all clouds. There is no opt-out. Microsoft is treating this as a security fix under their Secure Future Initiative.
Step 3: Understanding Your Impact
Microsoft provides a script to identify applications granted these low-privilege permissions in your tenant. It's a reasonable starting point, but you'll quickly find that the sheer number of SSO applications with User.Read is overwhelming. We need to be more targeted than that.
The applications you find will fall into one of two buckets: those using an authentication flow that is fundamentally incompatible with MFA, and those that already authenticate interactively but aren't currently conforming to all your CA policies.
Resource Owner Password Credential (ROPC) Applications
MFA is simply not available to applications which leverage ROPC because there is no interactive browser session navigating to login.microsoftonline.com. Without a browser, there is no opportunity for a challenge-response flow.
Users will receive the following error:
AADSTS50076
Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access 'Jamf'.
These applications must be re-engineered in one of the following ways (ordered best to worst outcome):
Support a full interactive browser flow with OAuth Authorization Code + PKCE. This is the gold standard and should be the target for any modern application.
Move to Device Code Flow to offload the authentication to a separate device where the user can complete MFA interactively.
Remove their dependency on Microsoft Graph as a delegated scope and convert to application permissions with client credentials, then exclude the client as a resource if needed.
To be clear: ROPC applications will enter a failed state with full authentication failures if left unfixed after enforcement begins on your tenant.
From what we've seen in the field, ROPC usage tends to hide in places you wouldn't immediately think to look: legacy PowerShell scripts using Get-Credential piped into Connect-MgGraph, older print management systems, some MDM enrolment flows, kiosk authentication setups, and vendor-managed integrations where the authentication method was never reviewed after initial deployment. If you have a large application estate, these are worth auditing specifically.
Interactive OAuth Applications
The second bucket contains applications that already support interactive OAuth flows but may not currently be prompting for MFA because of the enforcement gap. For many of these, the March 27th change will be invisible to end users. Technologies like Windows Hello for Business or passkeys will seamlessly satisfy the MFA requirement in the background.
However, if your MFA experience isn't seamless, or you have device compliance requirements that the devices which access your application don't meet, users will encounter friction or outright blocks. If this is a concern, the SIEM queries in the next section will help you identify exactly which apps and users fall into this category.
In the large environments we've assessed, this is typically where the bulk of the impact sits. The apps don't break, but users who were never prompted for MFA when accessing these applications will start receiving prompts. For one organisation we worked with, a widely used workforce application had been quietly authenticating thousands of users per day through this exact gap without anyone realising. Nothing would have broken on March 27th, but every one of those users would have been hit with an unexpected MFA prompt.
Microsoft's Recommended Baseline
Before we get into detection, it's worth calling out that Microsoft's official recommendation is to create a baseline MFA policy that targets all users and all resources without any resource exclusions whatsoever. This avoids the entire class of problem we're discussing. If you can achieve this, even with limited user/group exclusions for break-glass accounts, it is the strongest posture and the simplest to maintain. See: Require multifactor authentication for all users.
In practice, most organisations can't get to this baseline overnight because of legacy application dependencies. That's fine. The queries and remediation steps below will help you work toward it incrementally.
SIEM Detections
If you need to go deeper, the sign-in logs are your best friend. For your convenience I have compiled the queries for both Microsoft Sentinel and Splunk to surface the authentication flows that will be affected in your environment.
All of the queries below use a common technique: extracting the actual OAuth scopes from the authenticationProcessingDetails array in the sign-in event, then filtering out any sign-in that requested high-privilege scopes. What remains are the sign-ins that were only requesting the low-privilege scopes affected by this change. This is far more precise than just looking at which resource was targeted.
Note: The Splunk queries below use sourcetype="azure:monitor:aad" which is the standard sourcetype for Entra ID logs ingested via Azure Diagnostic Settings and Event Hub. Adjust the index value to match your environment.
Important caveat on scope data: The "Oauth Scope Info" key inside authenticationProcessingDetails is not always populated. Some legacy authentication flows, certain error states, and some first-party Microsoft app sign-ins may not include this field. When the field is absent, the queries below will silently drop those events. This means your results represent a lower bound, not a complete picture. If you need full coverage, combine these scope-based queries with the PowerShell script in the next section, which audits granted permissions regardless of what the sign-in logs record.
ROPC Detection
ROPC flows are the highest risk category. These applications authenticate by sending username and password directly to the token endpoint with no browser involved. They cannot handle MFA challenges and will fail outright when enforcement begins. This query finds any application that has been using ROPC to request only low-privilege scopes against Microsoft Graph or Azure AD Graph.
Splunk SPL
index="<your_entra_index>" sourcetype="azure:monitor:aad"
(properties.resourceId="00000003-0000-0000-c000-000000000000"
OR properties.resourceId="00000002-0000-0000-c000-000000000000")
properties.authenticationProtocol="ropc"
("User.Read" OR "People.Read" OR "GroupMember.Read" OR "Member.Read")
| spath path=properties.authenticationProcessingDetails{}.key output=apd_key
| spath path=properties.authenticationProcessingDetails{}.value output=apd_value
| eval pair = mvzip(apd_key, apd_value, "||")
| mvexpand pair
| eval apd_key = mvindex(split(pair, "||"), 0),
apd_value = mvindex(split(pair, "||"), 1)
| search apd_key="Oauth Scope Info"
| eval scopes_raw = apd_value
| eval scopes = split(scopes_raw, " ")
| eval scopes = mvmap(scopes, trim(scopes))
| eval scopes_key = mvjoin(scopes, " ")
| where NOT like(scopes_key, "%Write%")
AND NOT like(scopes_key, "%Sites%")
AND NOT like(scopes_key, "%Files%")
AND NOT like(scopes_key, "%Calendar%")
AND NOT like(scopes_key, "%Application%")
AND NOT like(scopes_key, "%Organization%")
AND NOT like(scopes_key, "%Directory%")
| stats count, dc(identity) AS users
by properties.authenticationProtocol,
properties.resourceDisplayName,
properties.authenticationRequirement,
properties.appDisplayName, scopes_key
Microsoft Sentinel (KQL)
union SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(30d)
| where AuthenticationProtocol == "ropc"
| where ResourceId in (
"00000002-0000-0000-c000-000000000000", // Azure AD Graph
"00000003-0000-0000-c000-000000000000" // Microsoft Graph
)
| mv-apply detail = AuthenticationProcessingDetails on (
where detail.key == "Oauth Scope Info"
| project Scopes = tostring(detail.value)
)
| where Scopes !contains "Write"
and Scopes !contains "Sites"
and Scopes !contains "Files"
and Scopes !contains "Calendar"
and Scopes !contains "Application"
and Scopes !contains "Organization"
and Scopes !contains "Directory"
| summarize
SignInCount = count(),
DistinctUsers = dcount(UserPrincipalName),
LastSeen = max(TimeGenerated)
by AppDisplayName, AppId, ResourceDisplayName,
AuthenticationProtocol, AuthenticationRequirement, Scopes
| sort by SignInCount desc
Any results from these queries represent applications that will break after enforcement. These need immediate remediation before March 27th.
Non-Interactive Authentication Detection
Non-interactive sign-ins include refresh token exchanges, background token requests, and service-to-service flows on behalf of a user. This is where you will find background app activity that is currently bypassing CA because it only requests low-privilege scopes with single-factor authentication.
Splunk SPL
index="<your_entra_index>" sourcetype="azure:monitor:aad"
(properties.resourceId="00000003-0000-0000-c000-000000000000"
OR properties.resourceId="00000002-0000-0000-c000-000000000000")
properties.authenticationRequirement="singleFactorAuthentication"
category="NonInteractiveUserSignInLogs"
("User.Read" OR "People.Read" OR "GroupMember.Read" OR "Member.Read")
| spath path=properties.authenticationProcessingDetails{}.key output=apd_key
| spath path=properties.authenticationProcessingDetails{}.value output=apd_value
| eval pair = mvzip(apd_key, apd_value, "||")
| mvexpand pair
| eval apd_key = mvindex(split(pair, "||"), 0),
apd_value = mvindex(split(pair, "||"), 1)
| search apd_key="Oauth Scope Info"
| eval scopes_raw = apd_value
| eval scopes = split(scopes_raw, " ")
| eval scopes = mvmap(scopes, trim(scopes))
| eval scopes_key = mvjoin(scopes, " ")
| where NOT like(scopes_key, "%Write%")
AND NOT like(scopes_key, "%Sites%")
AND NOT like(scopes_key, "%Files%")
AND NOT like(scopes_key, "%Calendar%")
AND NOT like(scopes_key, "%Application%")
AND NOT like(scopes_key, "%Organization%")
AND NOT like(scopes_key, "%Directory%")
| stats count, dc(identity) AS users
by properties.authenticationProtocol,
properties.resourceDisplayName,
properties.authenticationRequirement,
properties.appDisplayName, scopes_key
Microsoft Sentinel (KQL)
AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(30d)
| where ResourceId in (
"00000002-0000-0000-c000-000000000000", // Azure AD Graph
"00000003-0000-0000-c000-000000000000" // Microsoft Graph
)
| where AuthenticationRequirement == "singleFactorAuthentication"
| mv-apply detail = AuthenticationProcessingDetails on (
where detail.key == "Oauth Scope Info"
| project Scopes = tostring(detail.value)
)
| where Scopes !contains "Write"
and Scopes !contains "Sites"
and Scopes !contains "Files"
and Scopes !contains "Calendar"
and Scopes !contains "Application"
and Scopes !contains "Organization"
and Scopes !contains "Directory"
| summarize
SignInCount = count(),
DistinctUsers = dcount(UserPrincipalName),
LastSeen = max(TimeGenerated)
by AppDisplayName, AppId, ResourceDisplayName,
AuthenticationProtocol, AuthenticationRequirement, Scopes
| sort by SignInCount desc
Interactive Sign-In Detection
Interactive sign-ins are lower risk because the application already supports a browser flow and can potentially handle a CA challenge. This query finds interactive sign-ins that are currently completing with only single-factor authentication against Graph APIs using low-privilege scopes. These users may start receiving MFA prompts after enforcement begins.
Splunk SPL
index="<your_entra_index>" sourcetype="azure:monitor:aad"
(properties.resourceId="00000003-0000-0000-c000-000000000000"
OR properties.resourceId="00000002-0000-0000-c000-000000000000")
properties.authenticationRequirement="singleFactorAuthentication"
category="SignInLogs"
("User.Read" OR "People.Read" OR "GroupMember.Read" OR "Member.Read")
| spath path=properties.authenticationProcessingDetails{}.key output=apd_key
| spath path=properties.authenticationProcessingDetails{}.value output=apd_value
| eval pair = mvzip(apd_key, apd_value, "||")
| mvexpand pair
| eval apd_key = mvindex(split(pair, "||"), 0),
apd_value = mvindex(split(pair, "||"), 1)
| search apd_key="Oauth Scope Info"
| eval scopes_raw = apd_value
| eval scopes = split(scopes_raw, " ")
| eval scopes = mvmap(scopes, trim(scopes))
| eval scopes_key = mvjoin(scopes, " ")
| where NOT like(scopes_key, "%Write%")
AND NOT like(scopes_key, "%Sites%")
AND NOT like(scopes_key, "%Files%")
AND NOT like(scopes_key, "%Calendar%")
AND NOT like(scopes_key, "%Application%")
AND NOT like(scopes_key, "%Organization%")
AND NOT like(scopes_key, "%Directory%")
| stats count, dc(identity) AS users
by properties.authenticationProtocol,
properties.resourceDisplayName,
properties.authenticationRequirement,
properties.appDisplayName, scopes_key
Microsoft Sentinel (KQL)
SigninLogs
| where TimeGenerated > ago(30d)
| where ResourceId in (
"00000002-0000-0000-c000-000000000000", // Azure AD Graph
"00000003-0000-0000-c000-000000000000" // Microsoft Graph
)
| where AuthenticationRequirement == "singleFactorAuthentication"
| where IsInteractive == true
| mv-apply detail = AuthenticationProcessingDetails on (
where detail.key == "Oauth Scope Info"
| project Scopes = tostring(detail.value)
)
| where Scopes !contains "Write"
and Scopes !contains "Sites"
and Scopes !contains "Files"
and Scopes !contains "Calendar"
and Scopes !contains "Application"
and Scopes !contains "Organization"
and Scopes !contains "Directory"
| summarize
SignInCount = count(),
DistinctUsers = dcount(UserPrincipalName),
LastSeen = max(TimeGenerated),
SampleUsers = make_set(UserPrincipalName, 5)
by AppDisplayName, AppId, ResourceDisplayName,
AuthenticationProtocol, AuthenticationRequirement, Scopes
| sort by SignInCount desc
Identifying Affected Applications applications via an Authentication Toolkit for Condtional Access Policy Testing
Use tools like M42 Auth Toolkit or TokenTactics v2 to test the authentication flows and their behaviour with your conditional access policies.

By requesting low scopes you can tell if your current conditional access policies allow or deny these lower scopes for internal applications.

In this example the ROPC authentication fails and shows that CA policies are working as expected. Be careful with this method as it can give a false sense of security for pathways which are blocked due to network location requirements.
Or you can use a scripting language of your choice to run the following:
$tenant = "TENANT_ID"
$user = "user@tenant.onmicrosoft.com"
$pw = "PASSWORD"
$r = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenant/oauth2/v2.0/token" -Method POST -Body @{
grant_type = "password"
client_id = "04b07795-a710-4e9b-9549-b0210d5d94c8"
scope = "https://graph.microsoft.com/User.Read openid profile offline_access"
username = $user
password = $pw
} -ErrorVariable err -ErrorAction SilentlyContinue
if ($err) {
$detail = ($err[0].ErrorDetails.Message | ConvertFrom-Json).error_description
Write-Host "[!] $detail" -ForegroundColor Red
} else {
$payload = $r.access_token.Split(".")[1].Replace("-","+").Replace("_","/")
while ($payload.Length % 4) { $payload += "=" }
$claims = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($payload)) | ConvertFrom-Json
Write-Host "Scopes: $($claims.scp)"
Write-Host "Auth methods: $($claims.amr -join ', ')"
if ($claims.amr -notcontains "mfa") {
Write-Host "[!] MFA NOT enforced - bypass active" -ForegroundColor Yellow
} else {
Write-Host "[+] MFA enforced - CA is evaluating" -ForegroundColor Green
}
}Identifying Affected Applications via PowerShell
SIEM analysis tells you what has been happening, but it's limited to the events where "Oauth Scope Info" was populated and to whatever log retention window you have available. You can also work proactively by identifying applications that have been granted the affected low-privilege scopes regardless of what the sign-in logs record. The following script connects to Microsoft Graph and enumerates service principals with delegated permissions matching the scopes that will come under enforcement.
# Requires: Microsoft.Graph.Applications, Microsoft.Graph.Authentication
# Permissions: Application.Read.All, DelegatedPermissionGrant.Read.All
Connect-MgGraph -Scopes "Application.Read.All","DelegatedPermissionGrant.Read.All"
$lowPrivScopes = @(
"email", "offline_access", "openid", "profile",
"User.Read", "User.Read.All", "User.ReadBasic.All",
"People.Read", "People.Read.All",
"GroupMember.Read.All", "Member.Read.Hidden"
)
# Get all OAuth2 permission grants (delegated permissions)
$grants = Get-MgOAuth2PermissionGrant -All
$results = foreach ($grant in $grants) {
$grantedScopes = $grant.Scope -split " " | Where-Object { $_ -ne "" }
$onlyLowPriv = ($grantedScopes | Where-Object { $_ -notin $lowPrivScopes }).Count -eq 0
if ($onlyLowPriv -and $grantedScopes.Count -gt 0) {
$sp = Get-MgServicePrincipal -ServicePrincipalId $grant.ClientId -ErrorAction SilentlyContinue
[PSCustomObject]@{
AppDisplayName = $sp.DisplayName
AppId = $sp.AppId
ServicePrincipalId = $grant.ClientId
GrantedScopes = ($grantedScopes -join ", ")
ConsentType = $grant.ConsentType
ResourceId = $grant.ResourceId
}
}
}
$results | Sort-Object AppDisplayName | Format-Table -AutoSize
# Optionally export to CSV for review
# $results | Export-Csv -Path "LowPrivScopeApps.csv" -NoTypeInformation
This script identifies applications that only have low-privilege scope grants. These are the ones most likely to be affected by the enforcement change, as they have no higher-privilege scopes that would have already triggered CA evaluation.
One important caveat: this script shows what permissions are granted, not what is actually requested at authentication time. An application may hold User.Read as its only grant but never actually authenticate with it, or it may authenticate frequently. Cross-reference these results with your SIEM findings to prioritise by actual usage.
Remediation Priority
Based on your SIEM findings and PowerShell output, you can categorise applications into the following priority buckets:
Critical (fix before March 27th)
Applications using ROPC that only request low-privilege scopes. These will fail outright. Re-engineer to Authorization Code Flow with PKCE, Device Code Flow, or client credentials.
High (test and monitor)
Interactive applications excluded from CA policies that only request low-privilege scopes and don't currently handle CA challenges. These may prompt users for MFA where they weren't prompted before. Verify they handle the claims challenge parameter correctly per the Conditional Access developer guidance.
Medium (review exclusion rationale)
Applications that are excluded from your CA policies for historical reasons. If the original reason for the exclusion is no longer valid, remove the exclusion entirely rather than working around the enforcement change.
Low (no action required)
Applications that request scopes beyond the low-privilege list are already subject to CA enforcement. No change in behaviour.
For organisations with a large application estate, correlating this data across SIEM sign-in telemetry, Graph API permission grants, and CA policy configuration can be a significant effort. This is one of the use cases we built Modern 42 Identity Observability to address: giving you a single view across your identity ecosystem so you can see which applications are affected and how your users are actually authenticating, without manually stitching together data from three different sources.
If you're unsure about the scope of your exposure, or you need to move quickly and want a second pair of eyes on your CA policies, this is the kind of assessment our team does regularly for enterprise and SMB.





Comments