SOC Operations

Hunting for Threats in Entra ID: Sign-In Logs, Audit Logs, and What They Actually Tell You

Entra ID logs contain the evidence for most modern identity attacks — token theft, AiTM phishing, OAuth abuse, privilege escalation. The challenge isn't access to the data; it's knowing which fields to look at and how to query them. These KQL hunts surface the patterns that matter.

Modern identity-based attacks — token theft, adversary-in-the-middle phishing, OAuth consent grants, service principal abuse — leave their evidence in Entra ID logs. The problem most SOC teams face isn't a lack of data; it's the volume and specificity of signals that make manual review impractical. KQL hunts in Microsoft Sentinel bridge that gap by surfacing the patterns that indicate active compromise or high-risk behavior, rather than requiring an analyst to manually review millions of sign-in events.

This guide covers the key log sources, what each one tells you, and specific KQL queries you can run in Sentinel or Log Analytics today. Prerequisites: Entra ID data connector enabled in Sentinel, sign-in logs and audit logs streaming to your Log Analytics workspace. All queries target the SigninLogs, AADNonInteractiveUserSignInLogs, and AuditLogs tables.

Understanding Your Log Sources

SigninLogs

Interactive sign-in events — user-initiated logins through browsers and applications. This table contains geolocation, device, application, risk level, MFA method used, conditional access policy results, and IP address. The ResultType field indicates success (0) or failure (specific error codes). This is your primary table for detecting credential attacks, impossible travel, and token reuse.

AADNonInteractiveUserSignInLogs

Non-interactive sign-ins — token refresh operations, background service authentication, and application-to-application auth flows. Token theft frequently shows up here first, because a stolen token is used for silent authentication without prompting the user. This table is often overlooked but critical for detecting session hijacking.

AuditLogs

Administrative and identity change events — role assignments, application consent grants, user creation, group membership changes, PIM activations, and more. Privilege escalation and persistence mechanisms leave evidence here. The OperationName and TargetResources fields are your primary pivot points.

AADRiskyUsers and AADRiskySignIns

Entra ID Protection risk signals — Microsoft's assessment of user and sign-in risk based on behavioral and threat intelligence signals. These tables surface anomalous token issuance, leaked credentials, and suspicious sign-in properties. A high-risk user that hasn't been investigated is a finding in itself.

Hunt 1: Token Theft — Anomalous Token Reuse

MITRE ATT&CK: T1528 (Steal Application Access Token)

Token theft attacks — including AiTM phishing campaigns like those documented against Storm-0558 targets — steal the session token after successful authentication, bypassing MFA entirely. The stolen token is then replayed from a different IP or device. Key indicators: successful sign-in from a new IP immediately following MFA, user agent mismatch between interactive and non-interactive sessions, and the same session ID appearing from multiple geographic locations.

// Detect anomalous non-interactive sign-ins following interactive sign-ins
// Token theft indicator: different IP for token refresh vs. original login
let InteractiveSignIns = SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType == 0  // Successful
| project
    UserPrincipalName,
    InteractiveIP = IPAddress,
    InteractiveLocation = Location,
    InteractiveTime = TimeGenerated,
    SessionId = tostring(SessionId),
    DeviceId = tostring(DeviceDetail.deviceId),
    UserAgent;

let NonInteractiveSignIns = AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(24h)
| where ResultType == 0
| project
    UserPrincipalName,
    NonInteractiveIP = IPAddress,
    NonInteractiveLocation = Location,
    NonInteractiveTime = TimeGenerated,
    SessionId = tostring(CorrelationId),
    AppDisplayName;

InteractiveSignIns
| join kind=inner NonInteractiveSignIns on UserPrincipalName
| where NonInteractiveIP != InteractiveIP
| where abs(datetime_diff('minute', NonInteractiveTime, InteractiveTime)) < 60
| where InteractiveLocation != NonInteractiveLocation
| project
    UserPrincipalName,
    InteractiveIP,
    NonInteractiveIP,
    InteractiveLocation,
    NonInteractiveLocation,
    InteractiveTime,
    NonInteractiveTime,
    AppDisplayName
| order by InteractiveTime desc

Hunt 2: AiTM Phishing — Impossible Travel with Immediate Mailbox Activity

MITRE ATT&CK: T1566.002 (Spearphishing Link), T1078.004 (Valid Accounts: Cloud Accounts)

Adversary-in-the-middle phishing kits (EvilGinx, Modlishka, and similar) proxy the login page, capture credentials and session tokens in real time, and immediately establish attacker access from a different location. The pattern: successful login from one location, followed within minutes by successful access from a different country, followed by mailbox rule creation or bulk email reading.

// AiTM detection: impossible travel + post-authentication mailbox activity
// Look for sign-ins from two different countries within 30 minutes
SigninLogs
| where TimeGenerated > ago(7d)
| where ResultType == 0
| extend Country = tostring(LocationDetails.countryOrRegion)
| summarize
    Countries = make_set(Country),
    IPs = make_set(IPAddress),
    SignInCount = count(),
    FirstSignIn = min(TimeGenerated),
    LastSignIn = max(TimeGenerated)
  by UserPrincipalName, bin(TimeGenerated, 30m)
| where array_length(Countries) > 1
| extend TravelSpanMinutes = datetime_diff('minute', LastSignIn, FirstSignIn)
| where TravelSpanMinutes < 30
| project UserPrincipalName, Countries, IPs, TravelSpanMinutes, FirstSignIn
| order by FirstSignIn desc
// Detect inbox rule creation after anomalous sign-in (AiTM persistence indicator)
// MITRE T1564.008 (Hide Artifacts: Email Hiding Rules)
AuditLogs
| where TimeGenerated > ago(7d)
| where OperationName in (
    "New-InboxRule",
    "Set-InboxRule",
    "UpdateInboxRules"
  )
| extend
    InitiatedBy = tostring(InitiatedBy.user.userPrincipalName),
    TargetUser = tostring(TargetResources[0].userPrincipalName)
| project TimeGenerated, InitiatedBy, TargetUser, OperationName, CorrelationId
| order by TimeGenerated desc

Hunt 3: Suspicious OAuth App Consent Grants

MITRE ATT&CK: T1550.001 (Use Alternate Authentication Material: Application Access Token)

OAuth phishing (illicit consent grants) tricks users into authorizing malicious applications access to their Microsoft 365 data. The application then has persistent access without needing credentials. Key signals: non-admin consent events, high-risk permissions (Mail.ReadWrite, Files.ReadWrite.All), and applications from unverified publishers.

// Detect suspicious OAuth consent events — non-admin grants to high-privilege apps
AuditLogs
| where TimeGenerated > ago(30d)
| where OperationName in (
    "Consent to application",
    "Add delegated permission grant",
    "Add app role assignment to service principal"
  )
| extend
    ConsentedBy = tostring(InitiatedBy.user.userPrincipalName),
    IsAdminConsent = tostring(TargetResources[0].modifiedProperties
      | where Name == "ConsentContext.IsAdminConsent"
      | project NewValue),
    AppName = tostring(TargetResources[0].displayName),
    Permissions = tostring(TargetResources)
| where IsAdminConsent == "\"false\""  // User (non-admin) consent
| where Permissions has_any (
    "Mail.ReadWrite",
    "Files.ReadWrite.All",
    "offline_access",
    "User.Read.All",
    "Contacts.ReadWrite"
  )
| project TimeGenerated, ConsentedBy, AppName, IsAdminConsent, Permissions, CorrelationId
| order by TimeGenerated desc
// Find new service principals added to the tenant (potential OAuth persistence)
AuditLogs
| where TimeGenerated > ago(7d)
| where OperationName == "Add service principal"
| extend
    AddedBy = tostring(InitiatedBy.user.userPrincipalName),
    AppName = tostring(TargetResources[0].displayName),
    AppId = tostring(TargetResources[0].modifiedProperties
      | where Name == "AppId" | project NewValue)
| project TimeGenerated, AddedBy, AppName, AppId, CorrelationId
| order by TimeGenerated desc

Hunt 4: Privilege Escalation — Role Assignments and PIM Activations

MITRE ATT&CK: T1098.003 (Account Manipulation: Additional Cloud Roles)

Attackers who gain access to a cloud identity will attempt to escalate privileges by assigning themselves or their persistence mechanism to privileged roles. Monitor for role assignments outside PIM workflows, unexpected PIM activations (especially for roles not normally used by the activating account), and role assignments made outside business hours.

// Detect direct role assignments (bypassing PIM — should not happen in hardened environments)
AuditLogs
| where TimeGenerated > ago(30d)
| where OperationName in (
    "Add member to role",
    "Add eligible member to role"
  )
| extend
    AssignedBy = tostring(InitiatedBy.user.userPrincipalName),
    AssignedTo = tostring(TargetResources[0].userPrincipalName),
    RoleName = tostring(TargetResources[0].modifiedProperties
      | where Name == "Role.DisplayName" | project NewValue)
| where RoleName in (
    "\"Global Administrator\"",
    "\"Privileged Role Administrator\"",
    "\"Security Administrator\"",
    "\"Exchange Administrator\"",
    "\"Application Administrator\""
  )
| project TimeGenerated, AssignedBy, AssignedTo, RoleName, CorrelationId
| order by TimeGenerated desc
// Detect PIM activations — useful for baselining and spotting unusual activations
AuditLogs
| where TimeGenerated > ago(7d)
| where OperationName == "Add member to role in PIM requested (permanent)"
    or OperationName == "Add member to role in PIM completed (permanent)"
    or OperationName startswith "Add member to role in PIM"
| extend
    ActivatedBy = tostring(InitiatedBy.user.userPrincipalName),
    RoleName = tostring(TargetResources[0].displayName),
    Justification = tostring(AdditionalDetails | where Key == "Justification" | project Value)
| project TimeGenerated, ActivatedBy, RoleName, Justification, CorrelationId
| order by TimeGenerated desc

Hunt 5: Service Principal Abuse

MITRE ATT&CK: T1078.004 (Valid Accounts: Cloud Accounts)

Service principals — the identity that applications use to authenticate to Microsoft 365 and Azure — are increasingly targeted because they often hold high-privilege permissions, have long-lived credentials (client secrets or certificates), and are less monitored than user accounts. Signs of service principal abuse: authentication from unexpected IPs, unusual activity hours, access to resources outside the application's normal scope.

// Detect service principal sign-ins from unusual locations
// Requires AADServicePrincipalSignInLogs table
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(7d)
| where ResultType == 0
| summarize
    Countries = make_set(tostring(LocationDetails.countryOrRegion)),
    IPs = make_set(IPAddress),
    SignInCount = count()
  by ServicePrincipalName, AppId
| where array_length(Countries) > 2  // SP signing in from multiple countries is unusual
| order by SignInCount desc
// Detect new credentials added to service principals (persistence technique)
// MITRE T1098.001 (Additional Cloud Credentials)
AuditLogs
| where TimeGenerated > ago(30d)
| where OperationName in (
    "Add service principal credentials",
    "Update application – Certificates and secrets management"
  )
| extend
    ModifiedBy = tostring(InitiatedBy.user.userPrincipalName),
    AppName = tostring(TargetResources[0].displayName),
    CredentialType = tostring(TargetResources[0].modifiedProperties
      | where Name == "KeyType" | project NewValue)
| project TimeGenerated, ModifiedBy, AppName, CredentialType, CorrelationId
| order by TimeGenerated desc

Hunt 6: Bulk Data Access and Exfiltration Indicators

MITRE ATT&CK: T1530 (Data from Cloud Storage Object)

After establishing access, attackers perform bulk downloads of mailbox content, SharePoint files, or Teams messages. These operations appear in Microsoft 365 Unified Audit Logs (OfficeActivity table in Sentinel) as high-volume file or mail access events from the compromised account.

// Detect bulk mailbox access — unusual volume of email reads
OfficeActivity
| where TimeGenerated > ago(7d)
| where Operation in ("MailItemsAccessed", "FolderBindings")
| where ResultStatus == "Succeeded"
| summarize
    OperationCount = count(),
    UniqueItems = dcount(ItemId)
  by UserId, ClientInfoString, ClientIPAddress, bin(TimeGenerated, 1h)
| where OperationCount > 500  // Threshold: tune based on your environment baseline
| order by OperationCount desc
// Detect bulk file downloads from SharePoint/OneDrive
OfficeActivity
| where TimeGenerated > ago(7d)
| where Operation in ("FileDownloaded", "FileSyncDownloadedFull")
| summarize
    FileCount = count(),
    Sites = make_set(SiteUrl, 10),
    IPs = make_set(ClientIP, 5)
  by UserId, bin(TimeGenerated, 1h)
| where FileCount > 100  // Tune to environment baseline
| order by FileCount desc

Hunt 7: Cross-Tenant Access Anomalies

MITRE ATT&CK: T1199 (Trusted Relationship)

Cross-tenant access abuse is a growing attack vector — attackers use compromised guest accounts or manipulated cross-tenant trust settings to move between organizations. Hunt for unexpected cross-tenant sign-ins, guests accessing resources beyond their normal scope, and newly created cross-tenant trust relationships.

// Detect external (cross-tenant) sign-ins — users from other tenants accessing your resources
SigninLogs
| where TimeGenerated > ago(7d)
| where HomeTenantId != ResourceTenantId  // Sign-in is cross-tenant
| where ResultType == 0
| summarize
    AccessCount = count(),
    Resources = make_set(ResourceDisplayName, 10),
    IPs = make_set(IPAddress, 5)
  by UserPrincipalName, HomeTenantId, ResourceTenantId
| order by AccessCount desc
// Detect changes to cross-tenant access settings (potential trust manipulation)
AuditLogs
| where TimeGenerated > ago(30d)
| where OperationName in (
    "Update a partner specific cross-tenant access setting",
    "Create a partner specific cross-tenant access setting",
    "Delete a partner specific cross-tenant access setting"
  )
| extend
    ModifiedBy = tostring(InitiatedBy.user.userPrincipalName),
    TargetTenant = tostring(TargetResources[0].displayName)
| project TimeGenerated, ModifiedBy, TargetTenant, OperationName
| order by TimeGenerated desc

Operationalizing These Hunts

Running these queries manually on a recurring basis isn't sustainable. Convert the highest-fidelity queries into Sentinel Scheduled Analytics Rules — they run on a defined cadence, generate incidents when thresholds are met, and feed into your incident response workflow.

For each query you convert to a rule:

  1. Establish a baseline: run the hunt over 30 days of historical data to understand what's normal in your environment before alerting on every result
  2. Tune thresholds: the file download query with a threshold of 100 files per hour may be too noisy or too quiet for your specific environment — adjust based on your baseline
  3. Add entity mapping: map the UserPrincipalName, IPAddress, and AppDisplayName fields to Sentinel entities so incidents are enriched automatically
  4. Set the appropriate alert severity: token theft and role assignment queries should be High; bulk download queries may be Medium pending validation
// Example: Convert Hunt 3 (OAuth consent) to a Sentinel rule
// In Sentinel Analytics Rules, schedule to run every hour, looking back 1 hour
// Add entity mapping:
// - Account: ConsentedBy (userPrincipalName)
// - Application: AppName (Name)
// Alert title: "Suspicious OAuth User Consent to High-Privilege Application"
// Severity: High

What Entra ID Logs Won't Tell You

A critical limitation: Entra ID sign-in and audit logs only retain data for 30 days at the P1 license tier and 90 days at P2. An attacker who establishes access and waits beyond that window before performing visible actions will not appear in log queries. This makes streaming to a long-retention Log Analytics workspace mandatory — not optional — for any meaningful threat hunting program. Configure your Entra ID diagnostic settings to stream to Sentinel immediately, before you need the data.

Also note that non-interactive sign-in logs and service principal logs are separate tables that require explicit connector configuration. Many organizations enable the Entra ID connector but miss these additional log streams, creating significant blind spots in the identity attack surface.

Need help building an Entra ID threat hunting program?

Our team designs and deploys detection-in-depth programs for Microsoft identity environments — from log source configuration and KQL hunt development through incident response playbooks and analyst training. We find what automated detections miss.