Skip to content

Tutorials

Investigating a suspicious sign-in in Entra ID, step by step

What to check in the sign-in logs, which KQL queries to run, how to compare against the user's normal behavior, and when to revoke sessions and reset access.

Published 4 min readBeginner

An alert lands in your queue: a risky sign-in for one of your users, from a country they have never logged in from. It might be an attacker holding a stolen password. It might also be the user on holiday, a new VPN, or a mobile carrier routing traffic through another region.

Your job is not to guess. It is to collect enough evidence to make a confident call, and then act in proportion to what you found. This walkthrough follows the order most SOC analysts use, with the KQL to run at each step.

Step 1: read the alert before you query anything

Open the alert or the risk detection in Entra ID Protection and write down four facts: the user, the time, the IP address and the detection type. Detection types such as unfamiliar sign-in properties, anonymous IP address or atypical travel each point you toward a different question.

Then check two things that change the urgency of everything else:

  • Did the sign-in succeed? A blocked attempt with the correct password is still serious, but it means the attacker did not get in through this sign-in.
  • Was MFA satisfied? A successful single-factor sign-in from an unknown location is far more urgent than one that was challenged and failed.

Step 2: pull the sign-in and the days around it

Start with everything the account did in the last week. Seeing the suspicious sign-in next to normal activity makes anomalies much easier to spot.

user-signins.kql
SigninLogs
| where TimeGenerated > ago(7d)
| where UserPrincipalName =~ "jordan.lee@contoso.com"
| project TimeGenerated, IPAddress, Location, AppDisplayName, ClientAppUsed,
    ResultType, ResultDescription,
    AuthenticationRequirement, ConditionalAccessStatus,
    RiskLevelDuringSignIn,
    OS = tostring(DeviceDetail.operatingSystem), Browser = tostring(DeviceDetail.browser)
| order by TimeGenerated desc

The highlighted lines are the ones that usually decide the case. Read them like this:

  • ResultType is 0 for success. Codes such as 50126 (wrong password) or 500121 (strong authentication failed) tell you how far the attempt got. Many 50126 failures across different accounts from one IP address point to a password spray.
  • AuthenticationRequirement shows whether the sign-in needed one factor or several.
  • ConditionalAccessStatus tells you whether your Conditional Access policies applied, and whether they blocked the attempt.
  • The operating system and browser should look like the user's normal devices. A Windows user suddenly signing in from a Linux machine with a scripted user agent deserves attention.

Step 3: compare against the user's normal behavior

One unusual sign-in means little without a baseline. This query summarizes where the user successfully signed in over the previous 30 days, excluding the last day so the suspicious activity does not pollute the baseline.

baseline.kql
let user = "jordan.lee@contoso.com";
SigninLogs
| where TimeGenerated between (ago(30d) .. ago(1d))
| where UserPrincipalName =~ user and ResultType == "0"
| summarize SignIns = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
    by Location, IPAddress
| order by SignIns desc

If the suspicious IP address or country appears here with dozens of sign-ins over several weeks, you are probably looking at normal behavior that the detection did not recognize yet. If it has never appeared, keep going.

Ask the user, but through a separate channel

Contacting the user is a valid investigation step, not a shortcut. Use a channel the attacker cannot control: a phone call to a known number or a message to their manager. Never ask "was this you?" by email to the account you suspect is compromised.

Step 4: look for what happened after the sign-in

Attackers who get in tend to do the same few things quickly: register their own MFA method so they can return, and hide their tracks in the mailbox. Check both.

post-signin-activity.kql
let user = "jordan.lee@contoso.com";
AuditLogs
| where TimeGenerated > ago(7d)
| where OperationName in ("User registered security info", "User changed default security info", "User deleted security info")
| where tostring(TargetResources[0].userPrincipalName) =~ user
| project TimeGenerated, OperationName, Result, InitiatedBy = tostring(InitiatedBy.user.userPrincipalName)
inbox-rules.kql
let user = "jordan.lee@contoso.com";
OfficeActivity
| where TimeGenerated > ago(7d)
| where Operation in ("New-InboxRule", "Set-InboxRule", "UpdateInboxRules")
| where UserId =~ user
| project TimeGenerated, Operation, ClientIP, Parameters

Step 5: decide and contain

At this point you should be able to place the case in one of three buckets: benign (expected travel or a known network), suspicious but unconfirmed, or confirmed compromise. For a confirmed compromise, contain in this order:

  1. Revoke the user's sessions

    This invalidates refresh tokens, so the attacker has to sign in again.

    revoke-sessions.ps1
    Connect-MgGraph -Scopes "User.RevokeSessions.All"
    Revoke-MgUserSignInSession -UserId "jordan.lee@contoso.com"
  2. Reset the password and review MFA methods

    Remove any authentication method the user does not recognize, especially one registered during the suspicious window.

  3. Confirm the user as compromised in Identity Protection

    This raises the user risk, feeds Microsoft's detections and triggers any risk-based Conditional Access policies you have.

  4. Clean up and document

    Delete malicious inbox rules, check for mail forwarding and OAuth app consents, and write down the timeline while it is fresh.

What to write in the ticket

A good investigation note lets someone else reach the same conclusion without redoing your work. Include the alert facts from step 1, the result of the baseline comparison, anything you found after the sign-in, the actions you took with timestamps, and your verdict. When the next alert for this user arrives, that note will save the next analyst most of the work.

Terms in this article

KQL
The read-only query language used by Microsoft Sentinel, Defender XDR advanced hunting, Azure Monitor and Azure Data Explorer.
Multi-factor authentication
Signing in with two or more different kinds of proof, such as a password plus a phone or security key, so a stolen password alone is not enough.
Conditional Access
Microsoft Entra ID's policy engine that decides, at each sign-in, whether to allow access, require MFA or another control, or block.
Password spray
An attack that tries a few common passwords against many accounts, staying under lockout limits that per-account guessing would trigger.
Business email compromise
Fraud in which an attacker impersonates or takes over a trusted business email account to redirect payments or obtain sensitive data.

Last reviewed on