Most enterprise environments are hybrid now — on-premises Active Directory synced to Entra ID, with the same credentials used to access cloud resources. Attackers know this and exploit it actively. A compromised on-prem account becomes a compromised cloud identity. A stolen Entra ID token can be replayed from anywhere in the world without triggering most endpoint detections.

Entra ID sign-in logs in Microsoft Sentinel are one of the richest detection data sources available — but most environments are either not ingesting them at the right verbosity or not building detections that actually catch the techniques being used. Here are the queries that work in production.

Free Download
SOC Alert Triage Checklist
Severity matrix, 5-phase triage process, critical Event IDs, Splunk quick reference. One page, instant download.
Get Free Checklist →
Log sources required

All queries below require Entra ID Sign-in Logs and Audit Logs ingested into Microsoft Sentinel via the Microsoft Entra ID connector. Ensure both SigninLogs and AuditLogs tables are being populated before building these detections. Also verify that non-interactive sign-in logs are enabled — many token theft patterns only appear in non-interactive logs.

1. MFA Bypass — Adversary-in-the-Middle (AiTM)

AiTM phishing proxies sit between the user and the legitimate login page, relaying authentication in real time and capturing the post-MFA session token. From Entra ID's perspective, the authentication looks completely legitimate — MFA was satisfied, the user successfully logged in. The signal is in what happens after the session is established: the token gets replayed from a different IP or location than where the original authentication occurred.

Sentinel KQL — AiTM token replay detection
let AuthWindow = 1h;
SigninLogs
| where TimeGenerated > ago(1d)
| where ResultType == "0"
| where AuthenticationRequirement == "multiFactorAuthentication"
| summarize
    Locations = make_set(Location),
    IPs = make_set(IPAddress),
    UserAgents = make_set(UserAgent),
    AuthCount = count()
    by UserPrincipalName, bin(TimeGenerated, AuthWindow)
// Token replay: same user, multiple IPs or locations
// in the same window after successful MFA
| where array_length(IPs) > 1
    or array_length(Locations) > 1
| order by AuthCount desc

2. Impossible Travel

Two successful sign-ins from the same account in locations that are physically impossible to travel between in the time window between them. The challenge is building this correctly — many VPN configurations and cloud provider IP ranges generate false positives if you don't filter them out.

Sentinel KQL — Impossible travel detection
let ImpossibleSpeedKmH = 800;
SigninLogs
| where TimeGenerated > ago(1d)
| where ResultType == "0"
| where isnotempty(LocationDetails)
| extend
    Lat = toreal(LocationDetails.geoCoordinates.latitude),
    Lon = toreal(LocationDetails.geoCoordinates.longitude)
| where isnotempty(Lat)
| sort by UserPrincipalName, TimeGenerated asc
| serialize
| extend
    PrevLat = prev(Lat),
    PrevLon = prev(Lon),
    PrevTime = prev(TimeGenerated),
    PrevUser = prev(UserPrincipalName)
| where UserPrincipalName == PrevUser
| extend TimeDiffHours =
    datetime_diff('minute',
    TimeGenerated, PrevTime) / 60.0
| extend DistanceKm = geo_distance_2points(
    Lon, Lat, PrevLon, PrevLat) / 1000
| where TimeDiffHours > 0
| extend SpeedKmH = DistanceKm / TimeDiffHours
| where SpeedKmH > ImpossibleSpeedKmH
| project TimeGenerated, UserPrincipalName,
    Location, IPAddress, SpeedKmH, DistanceKm
| order by SpeedKmH desc

3. Entra ID Privileged Role Changes

Any change to Global Administrator, Privileged Role Administrator, or other highly privileged roles in Entra ID should alert immediately. Attackers who compromise a privileged account often add a second account to a high-privilege role as a persistence mechanism — and this is exactly the event that gets missed when teams are focused on endpoint detections.

Sentinel KQL — Privileged role assignment detection
AuditLogs
| where TimeGenerated > ago(7d)
| where OperationName in(
    "Add member to role",
    "Add eligible member to role",
    "Add scoped member to role"
  )
| extend RoleName = tostring(
    TargetResources[0].displayName)
| where RoleName in~(
    "Global Administrator",
    "Privileged Role Administrator",
    "Security Administrator",
    "Exchange Administrator",
    "SharePoint Administrator",
    "Conditional Access Administrator"
  )
| extend
    InitiatedByUser = tostring(
        InitiatedBy.user.userPrincipalName),
    TargetUser = tostring(
        TargetResources[1].userPrincipalName)
| project TimeGenerated, OperationName,
    RoleName, InitiatedByUser, TargetUser,
    Result, CorrelationId
| order by TimeGenerated desc
P1 immediately

Any Global Administrator role assignment outside of a known provisioning process is a P1. Treat it the same way you'd treat a Domain Admins change in on-premises AD — contain first, investigate after.

4. Suspicious OAuth App Consent

Consent phishing is increasingly common — attackers trick users into granting an OAuth app access to their mailbox, files, or contacts. The app looks legitimate (often impersonating Microsoft or a known SaaS product) and once consent is granted, the attacker has persistent access that survives password resets and MFA changes.

Sentinel KQL — Suspicious OAuth consent grants
AuditLogs
| where TimeGenerated > ago(7d)
| where OperationName == "Consent to application"
| extend
    AppName = tostring(
        TargetResources[0].displayName),
    ConsentedBy = tostring(
        InitiatedBy.user.userPrincipalName),
    Permissions = tostring(
        AdditionalDetails)
| // Flag high-risk permission scopes
| where Permissions has_any(
    "Mail.ReadWrite", "Mail.Send",
    "Files.ReadWrite.All",
    "Directory.ReadWrite.All",
    "RoleManagement.ReadWrite.Directory"
  )
| project TimeGenerated, AppName,
    ConsentedBy, Permissions, Result
| order by TimeGenerated desc

5. Guest Account Abuse

Guest accounts in Entra ID are a common blind spot. External users invited to a tenant often have more access than intended, and compromised guest accounts from partner or vendor organizations are an increasingly common initial access vector. The signal is guest account activity that looks like internal user behavior.

Sentinel KQL — Guest account anomalous activity
SigninLogs
| where TimeGenerated > ago(7d)
| where UserType == "Guest"
| where ResultType == "0"
| summarize
    SigninCount = count(),
    AppAccessed = make_set(AppDisplayName),
    Locations = make_set(Location),
    IPs = make_set(IPAddress)
    by UserPrincipalName, bin(TimeGenerated, 1d)
// Flag guests accessing many apps or from many locations
| where array_length(AppAccessed) > 5
    or array_length(Locations) > 3
| order by SigninCount desc

6. Risky Sign-in Baseline

Entra ID Protection generates risk signals natively — but most environments have it configured to alert rather than block, and those alerts often sit in a queue nobody is watching. This query pulls high-risk sign-ins that succeeded despite the risk signal, which is the combination that matters most.

Sentinel KQL — Successful risky sign-ins
SigninLogs
| where TimeGenerated > ago(1d)
| where RiskLevelDuringSignIn in(
    "high", "medium")
| where ResultType == "0"
| project TimeGenerated, UserPrincipalName,
    RiskLevelDuringSignIn, RiskDetail,
    Location, IPAddress, AppDisplayName,
    AuthenticationRequirement
| order by RiskLevelDuringSignIn,
    TimeGenerated desc
Weekly Intelligence Pack
Cloud identity detection rules in the rotation

Entra ID and cloud identity detection is increasingly part of the weekly rotation — alongside on-premises Sentinel, CrowdStrike, and Splunk rules. Every issue includes the full detection with investigation steps, false positive guidance, and MITRE mapping. Platforms rotate so you get broad coverage over time.

Join — $14.99/mo See a Sample Issue
Founding member pricing locked for life · 30-day money back