A user who clicks "Accept" on a malicious app's consent screen hands the attacker a refresh token for their mailbox and files, and no password, MFA prompt or password reset stands in the way afterwards. This guide shows how to detect OAuth consent phishing in Microsoft Entra ID: which audit events and fields record a consent, which rules separate a phishing grant from a user adding a calendar tool, how to sweep grants that already exist, and how to respond without letting the app come straight back. It is written for SOC analysts, detection engineers and Entra administrators working in Microsoft Sentinel, Defender XDR or another SIEM fed with Entra audit logs. The attack itself, and how it differs from token theft and device code phishing, is explained in the Consent Phishing and Illicit OAuth Grants Guide; consent settings and the application model are in the Entra ID Applications and Consent Guide. This page is the detection runbook.
Key takeaways
- One event carries the signal: every grant writes "Consent to application" in the Entra audit log (category ApplicationManagement, service Core Directory), with the scopes in
ConsentAction.Permissionsand admin consent inConsentContext.IsAdminConsent. - Scopes decide severity: Microsoft reports that a combination of Mail, Contacts, MailboxSettings, People, Files and Notes permissions is the pattern most consent phishing attacks request;
offline_accessnext to mailbox scopes is the high-fidelity pairing. - Rarity beats allow lists: a consent by a user who has never consented before, or to an app nobody else in the tenant uses, is a better trigger than a list of bad app names, which attackers change daily.
- Sweep existing grants: delegated grants have no creation date on the Graph object and the portal shows admin consents for only 90 days, so pair the audit history with a full export of
oauth2PermissionGrantobjects. - Disable before you delete: Microsoft advises disabling a malicious service principal rather than deleting it, because a deleted app can return the next time another user consents.
When you need this
- User consent is still on: by default, Entra users can consent to permissions that do not need admin consent. Until consent is restricted, every user is a possible approver for an attacker's app.
- After a phishing campaign with no credential prompt: consent phishing links go to the real Microsoft sign-in page, so users and mail filters report "it was a genuine Microsoft page". The 2022 case where fraudulent partner accounts obtained the verified publisher badge shows the badge alone is not proof of trust. See the Microsoft Verified Publisher OAuth Phishing 2022 breach analysis.
- When mailbox rules or data access appear with no risky sign-in: an app acting on a delegated token produces Graph or EWS activity under the user's identity, often without any interactive sign-in. This is where business email compromise investigations stall if nobody checks consents.
Before you start
| Item | Where it lives | What you need from it |
|---|---|---|
| Consent event | Entra audit log; in Sentinel the AuditLogs table; also the Microsoft Purview audit log | OperationName "Consent to application", Result, InitiatedBy user and IP, TargetResources of type ServicePrincipal, AdditionalDetails User-Agent |
| Related events | Same log | "Add service principal" (first time the app appears in your tenant), "Add delegated permission grant" or "Add OAuth2PermissionGrant", "Add app role assignment to service principal" (application permissions), "Add service principal credentials" |
| Key modified properties | TargetResources[].modifiedProperties | ConsentContext.IsAdminConsent, ConsentAction.Permissions (string containing ConsentType: and Scope:), TargetId.ServicePrincipalNames |
| Blocked attempts | Same log, status reason "Risky application detected" | Risk-based step-up consent stopped a user and showed AADSTS90094; repeated attempts for one app mean a campaign |
| Current grants | Microsoft Graph oauth2PermissionGrant and appRoleAssignment | clientId, consentType (AllPrincipals or Principal), principalId, resourceId, scope |
| App behaviour | Defender for Cloud Apps app governance, if licensed | Alerts such as "OAuth App with suspicious Reply URL" and "App with suspicious OAuth scope made graph calls to read email and created inbox rule" |
- Latency: Microsoft states audit entries can take from 30 minutes to 24 hours to appear in audit search. Stream Entra audit logs to the SIEM through diagnostic settings rather than relying on portal searches, and set rule look-back windows that tolerate late events.
- Auditing must already be on: scoping what an app did depends on mailbox auditing and activity auditing having been enabled before the consent. Check this now, not during the incident.
- Test consent: register a harmless test app in a lab tenant, consent to
User.Readandoffline_accessas a normal user, and confirm the event reaches the SIEM with the scope string parsed. Field layouts change; match on property display names, not array positions. - Least privilege for the sweep: reading every grant needs directory-wide read permissions such as
Application.Read.All. Run the export under a dedicated identity with read-only scopes and a named owner. See the Service Account Security Guide.
Step-by-step
- Collect the events. Send Entra audit logs, sign-in logs and non-interactive user sign-in logs to the SIEM. Filter the consent rules on
LoggedByService == "Core Directory"andCategory == "ApplicationManagement", as Microsoft's own Sentinel rules do. - Parse the consent string. Extract
ConsentAction.Permissionsfrom the ServicePrincipal target, then parse outConsentTypeandScope. Split the scope string on spaces and compare exact scope names: Microsoft notes in its rule comments that term matching on "Mail.Read" also matchesMail.ReadBasic. - Build a consent baseline. Summarise 7 to 30 days of consents by initiating user and by app. Microsoft's "Rare application consent" rule excludes any user or app that consented more than three times in the previous seven days and alerts on the rest.
- Deploy the rules below in report mode for two weeks, labelling each hit as legitimate, policy violation or malicious.
- Run the grant sweep (next section) so you are not only watching new consents.
- Wire the response so that a confirmed hit disables the app, removes the grants and starts the mailbox scoping in one ticket.
Detection rules
| Rule | Logic | Why it works | Priority (ours) |
|---|---|---|---|
| Mailbox scopes with offline access | "Consent to application" where parsed scopes contain offline_access and any of Mail.Read, Mail.ReadWrite, Mail.Send, MailboxSettings.ReadWrite, consent type not AllPrincipals | The pairing gives long-lived silent mailbox access; Microsoft ships this as a High severity rule | High |
| Offline access to collaboration data by unknown app | Same, with Files, Chat, ChannelMessage, Notes, EWS.AccessAsUser.All or EAS.AccessAsUser.All scopes, app client ID not in your known-app list | Covers file and Teams exfiltration apps | High |
| Rare consenter or rare app | Consent by a user with no consent in the look-back, or to an app with no prior consent in the tenant | Phishing targets people who do not usually consent and apps that are new to you | Medium |
| Unexpected admin consent | ConsentContext.IsAdminConsent is True, or consent type AllPrincipals, without a matching change ticket or admin consent workflow approval | One admin click grants the app every user's data | High |
| Application permissions granted | "Add app role assignment to service principal" for a Microsoft Graph app role such as Mail.Read or Files.Read.All | App-only access needs no user at all | High |
| Burst | Same app consented by several users within an hour, or many "Risky application detected" entries for one app | A mass phishing campaign in progress | High |
| Consent after risky sign-in | Consent within 24 hours of a risky, unfamiliar-country or unfamiliar-ASN sign-in by the same user | An attacker in a session consents to keep access after the password is reset | High |
| Consent then credential | Consent to an app, followed by "Add service principal credentials" on the same service principal by a different actor | Shipped by Microsoft as "Credential added after admin consented to Application" | Medium |
| High-impact user | Any consent by executives, finance approvers or administrators | Microsoft's guidance singles out consents by high-value users | Medium |
A minimal KQL starting point for the first rule, adapted from Microsoft's published Sentinel rule "End-user consent to app with mailbox and offline access delegated scopes":
AuditLogs
| where TimeGenerated > ago(1d)
| where LoggedByService =~ "Core Directory" and Category =~ "ApplicationManagement"
| where OperationName =~ "Consent to application" and Result =~ "success"
| mv-apply T = TargetResources on (where T.type =~ "ServicePrincipal"
| extend AppName = tostring(T.displayName), SpId = tostring(T.id), Props = T.modifiedProperties)
| mv-apply P = Props on (where P.displayName =~ "ConsentAction.Permissions"
| extend Consent = trim(@'"', tostring(P.newValue)))
| parse Consent with * "ConsentType: " ConsentType ", Scope: " Scope "]" *
| extend Scopes = split(Scope, " ")
| where set_has_element(Scopes, "offline_access")
| where set_has_element(Scopes, "Mail.Read") or set_has_element(Scopes, "Mail.ReadWrite")
or set_has_element(Scopes, "Mail.Send") or set_has_element(Scopes, "MailboxSettings.ReadWrite")
| where ConsentType != "AllPrincipals"
| project TimeGenerated, User = tostring(InitiatedBy.user.userPrincipalName),
IP = tostring(InitiatedBy.user.ipAddress), AppName, SpId, ConsentType, Scope, CorrelationId
Use the same parse block for the other scope rules and change only the scope test. For the admin consent rule, read the ConsentContext.IsAdminConsent property the same way. The KQL for Threat Hunting Guide covers the operators used here.
Sweep existing grants
Alerts only see new consents. Grants made months ago, under looser settings, are still valid. Microsoft recommends a weekly review of consent grants in large tenants.
- Export delegated grants. List all
oauth2PermissionGrantobjects with Microsoft Graph (Get-MgOauth2PermissionGrant -All) or Microsoft'sGet-AzureADPSPermissions.ps1script, which writesPermissions.csv. ResolveclientId(the service principal object ID, not the appId) to the app name, publisher tenant and verified publisher status. - Export application permissions with
GET /servicePrincipals/{id}/appRoleAssignmentsfor every non-Microsoft service principal. - Flag the high-risk set: consent type AllPrincipals on non-Microsoft apps; any
Mail.*exceptMail.ReadBasic*,Contacts.*,MailboxSettings.*,People.*,Files.*,Notes.*,Directory.AccessAsUser.Alloruser_impersonation; and grants held by a single user to an app nobody else uses. - Inspect app metadata for Microsoft's red flags: misspelled, bland or "hacker-sounding" names, a reply URL on a recently registered, temporary or unrelated domain, no terms of service link, and a publishing tenant that is new.
- Join to the audit history for when and from where each flagged grant was made. Where the audit log no longer covers it, treat the grant as unexplained and ask the user.
| Finding | Likely meaning | Action |
|---|---|---|
| One user, unknown app, mailbox scopes plus offline_access | Consent phishing until proven otherwise | Triage now; disable the app pending answers |
| Many users, same unverified app, read scopes | Popular unsanctioned tool or a campaign | Check consent dates for a burst; route to app owner review |
| AllPrincipals grant, no change record | Admin consent outside process, or a compromised admin | Review the consenting admin's sign-ins; confirm with the admin |
| Grant to an app Microsoft has disabled | App removed by Microsoft for a terms violation | Remove grants and check what the app did before it was disabled |
| Old grant, app shows no sign-in activity for 90 days | Abandoned integration | Remove through normal access review |
Triage a consent alert
- Did the user mean to do it? Ask through a channel the attacker cannot see: a call, or the user's manager. Ask what the app was supposed to do and where the link came from.
- What is the app? Publisher, verified status, home tenant, reply URLs, creation date, number of users in your tenant who consented. Check Defender for Cloud Apps for app governance alerts on the same app ID.
- What did it do? Look for non-interactive sign-ins and Microsoft Graph activity with the app's ID after the consent time, mail read and send volume, new inbox rules, file downloads and sharing. App governance alerts such as "App creates inbox rule and made unusual email searches activities" answer this quickly where licensed.
- What else happened? Sign-ins before the consent, MFA method changes and new credentials on the service principal. See How to detect new credentials added to an app registration.
- Is it wider? Search the same app ID and the same sender or URL across all users and all consents in the look-back.
Respond to a malicious grant
- Preserve evidence: export the audit entries, the grant objects and the app's service principal details (including reply URLs) before you change anything.
- Disable the service principal (set sign-in to disabled on the enterprise application). Microsoft states a disabled app cannot obtain new tokens and users cannot sign in or consent to it, while a deleted app may return when another user consents.
- Remove the grants: delegated grants with
Remove-MgOauth2PermissionGrantorDELETE /oAuth2PermissionGrants/{id}, and application permissions by deleting the app role assignments. User consent grants cannot be revoked in the portal's User consent tab, so use Graph or PowerShell. - Revoke the user's sessions and refresh tokens for every affected user so already-issued tokens stop working sooner, and reset passwords where the sign-in history suggests the account itself was also compromised. See the OAuth Token Theft Incident Playbook.
- Clean the mailbox: remove inbox and forwarding rules the app created and check sent items for internal phishing. Follow the Business Email Compromise Response Playbook if mail was sent or read.
- Block re-consent: restrict user consent before closing the incident, because revoking a grant does not stop users consenting again.
- Contact Microsoft Support about the app, as Microsoft's guidance advises after disabling it.
Rollback: if an app turns out to be legitimate, re-enable the service principal and have the user or an administrator consent again through the admin consent workflow; record the decision so the rule can allow-list the app ID rather than the name.
Prevention that makes detection easier
| Consent setting | Policy ID or feature | Effect on detection | Fits |
|---|---|---|---|
| Allow user consent for apps | microsoft-user-default-legacy | Highest alert volume; every user can approve any non-admin permission | Not recommended |
| Allow user consent for apps from verified publishers, for selected permissions | microsoft-user-default-low, with permission classifications | Only low-impact permissions from verified publishers and your own apps; most phishing scopes need admin approval | Most organisations |
| Do not allow user consent | No policy assigned to the default user role | Every request becomes an admin decision; user consent alerts become rare and high signal | Regulated or high-risk tenants with a responsive approval team |
| Admin consent workflow | Enterprise apps, Consent and permissions, Admin consent settings | Gives a legitimate path, so unexplained admin consents stand out | All, alongside restricted user consent |
- Small organisations: choose verified publishers with low-impact permissions, turn on the admin consent workflow and run the High priority rules plus a monthly sweep.
- Large organisations: disable or tightly restrict user consent, put all consent requests through the workflow with a defined approval time, use app governance where licensed and run the sweep weekly.
Verify it worked
- Replay: in a test tenant, consent as a normal user to a test app requesting
Mail.Readandoffline_access; the High rule should fire within your target time allowing for audit latency. - Blocked path: after restricting consent, the same test should produce an admin consent request or "Risky application detected", not a grant.
- Removal: after response, re-list grants for the app and confirm none remain and no new tokens have been issued since the disable time.
Measures that show it is working
- Median time from "Consent to application" to analyst triage for High rules.
- Number of non-Microsoft apps holding AllPrincipals grants or mailbox scopes, trending down.
- Percentage of consents that went through the admin consent workflow.
- Grants found by the sweep that no alert had raised (should fall to near zero).
- Median admin consent request approval time.
Common failure patterns
- Allow listing by display name: attackers copy legitimate names. Allow-list app IDs and publisher tenant IDs.
- Term matching on scopes:
has "Mail.Read"also matchesMail.ReadBasicand inflates noise until the rule is muted. Split and compare exact scopes. - Short look-back windows: a one-hour rule window with up to 24 hours of audit latency silently misses events. Use a daily window or ingestion-time filtering.
- Deleting the app: the next user who clicks the link recreates it. Disable it and block consent first.
- Revoking grants but not sessions: tokens already issued keep working for their lifetime; combine grant removal with session revocation.
- Ignoring admin consent: the Midnight Blizzard intrusion at Microsoft shows how OAuth applications granted broad permissions turn one foothold into executive mailbox access. See the Microsoft Midnight Blizzard Breach 2024.
Framework mapping
| Framework | Reference | How this detection supports it |
|---|---|---|
| MITRE ATT&CK v20 | T1528 Steal Application Access Token; T1566.002 Spearphishing Link; T1550.001 Application Access Token; T1078.004 Cloud Accounts | Detects the grant that yields the token and the later use of it |
| NIST SP 800-53 Rev 5 | AC-6 Least Privilege; AC-2(12) Account Monitoring for Atypical Usage; AU-6 Audit Record Review, Analysis, and Reporting; SI-4 System Monitoring; CM-7 Least Functionality | Restricts and monitors third-party access granted by users |
| NIST CSF 2.0 | PR.AA-05; DE.CM-03; DE.AE-02 | Permission management, activity monitoring and event analysis |
Practitioner checklist
- Stream Entra audit, sign-in and non-interactive sign-in logs to the SIEM with a retention that covers your sweep look-back.
- Confirm mailbox and activity auditing is enabled for all users.
- Parse
ConsentAction.PermissionsandConsentContext.IsAdminConsent; match exact scope names. - Deploy mailbox plus offline_access, admin consent and burst rules first, in report mode.
- Export all delegated grants and app role assignments and review the high-risk set weekly or monthly.
- Respond in order: preserve, disable, remove grants, revoke sessions, clean mailbox, block re-consent, report.
- Restrict user consent to verified publishers and low-impact permissions, or disable it, and run the admin consent workflow.
- Scope the identity that runs the sweep to read-only permissions with a named owner.
Standards and references
- Microsoft Learn: App consent grant investigation playbook
- Microsoft Learn: Detect and remediate illicit consent grants
- Microsoft Learn: Configure how users consent to applications
- Microsoft Learn: Review permissions granted to enterprise applications
- Microsoft Learn: oAuth2PermissionGrant resource type (Microsoft Graph)
- Microsoft Learn: Investigate app governance threat detection alerts
- Microsoft: Azure Sentinel Microsoft Entra ID analytic rules
- MITRE ATT&CK: Steal Application Access Token (T1528)
Related NHI Mgmt Group resources: Consent Phishing and Illicit OAuth Grants Guide · Entra ID Applications and Consent Guide · Entra ID Sign-In and Audit Log Detection Guide · How to detect use of a stolen OAuth refresh token · MITRE ATT&CK T1528 Steal Application Access Token Guide