Entra sign-in logs record every authentication, and KQL turns them into detections. In this lab you route sign-in logs to a Log Analytics workspace, then write five queries: a baseline, password spray, legacy authentication, repeated MFA denials and first-time countries. You finish by saving one query as a scheduled alert.
Key takeaways
- Sign-in logs must be exported to Log Analytics before you can query them with KQL
- ResultType is the error code. 0 means success and 50126 means a wrong password
- Password spray looks like one IP address failing against many different users
- Save your best query as an alert rule so it runs without you
Why this lab matters
The Entra admin centre is fine for looking up one sign-in. It is not built for questions such as "which IP address failed against forty users in an hour?". Kusto Query Language (KQL) in Log Analytics answers that in seconds, and the same query can become an alert in Microsoft Sentinel or Azure Monitor.
What you need
- A test tenant with Microsoft Entra ID P1 or P2, which is required to export sign-in logs.
- An Azure subscription and a Log Analytics workspace.
- The Security Administrator role in Entra, and Log Analytics Contributor on the workspace.
Step 1: Export the logs
- In the Entra admin centre go to Identity, Monitoring and health, Diagnostic settings, Add diagnostic setting.
- Tick SignInLogs, NonInteractiveUserSignInLogs and AuditLogs.
- Choose Send to Log Analytics workspace, pick your workspace and save.
- Generate some data: sign in successfully a few times, and enter a wrong password for two or three test users.
New data can take 15 to 30 minutes to appear the first time. If a query returns nothing, check the time range before you change the query.
Step 2: Build a baseline
Open the workspace, select Logs and run:
SigninLogs
| where TimeGenerated > ago(24h)
| summarize Attempts = count(), Users = dcount(UserPrincipalName) by ResultType, ResultDescription
| order by Attempts desc
Learn the common codes in your tenant. The ones you will meet most:
| ResultType | Meaning |
|---|---|
| 0 | Success |
| 50126 | Invalid username or password |
| 50053 | Account locked by smart lockout |
| 50074 | Strong authentication required |
| 500121 | MFA failed or was denied |
| 53003 | Blocked by Conditional Access |
Step 3: Hunt for four patterns
Password spray
SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType in ("50126", "50053")
| summarize Failures = count(), TargetUsers = dcount(UserPrincipalName),
Sample = make_set(UserPrincipalName, 5)
by IPAddress, bin(TimeGenerated, 1h)
| where TargetUsers >= 10
| order by TargetUsers desc
One address failing against many accounts is the signature. Lower the threshold to 3 in a small test tenant so you can see your own test data.
Legacy authentication
union SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(7d)
| where ClientAppUsed !in ("Browser", "Mobile Apps and Desktop clients", "")
| summarize Attempts = count(), Successes = countif(ResultType == "0")
by UserPrincipalName, ClientAppUsed
| order by Successes desc
Repeated MFA denials
SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType == "500121"
| summarize Denials = count(), IPs = make_set(IPAddress, 5)
by UserPrincipalName, bin(TimeGenerated, 30m)
| where Denials >= 5
Many denied prompts in a short window can mean an attacker has the password and is hoping the user approves by mistake.
First sign-in from a new country
let known = SigninLogs
| where TimeGenerated between (ago(30d) .. ago(1d))
| where ResultType == "0"
| distinct UserPrincipalName, Location;
SigninLogs
| where TimeGenerated > ago(1d)
| where ResultType == "0" and isnotempty(Location)
| join kind = leftanti known on UserPrincipalName, Location
| project TimeGenerated, UserPrincipalName, Location, IPAddress, AppDisplayName
Step 4: Tune and schedule
- Pick the spray query. Add a line to exclude addresses you trust, for example your office or VPN egress:
| where IPAddress !in ("203.0.113.10"). - Select New alert rule. Measure table rows, threshold greater than 0, evaluate every hour over the last hour.
- Attach an action group that emails your test mailbox.
- Trigger it with wrong passwords against several test users and confirm the alert arrives.
You are finished when all five queries return sensible results on your own test data and one scheduled alert has fired and reached you.
Evidence to keep
- A screenshot of the diagnostic setting.
- The saved queries, exported or stored in a query pack.
- The alert rule and one fired alert.
Clean up
Delete the alert rule and action group. Remove the diagnostic setting if the workspace was created only for this lab, because stored logs are billed by volume.
Frequently asked questions
Which licence do I need to query Entra sign-in logs with KQL?
Exporting sign-in logs through diagnostic settings needs Microsoft Entra ID P1 or P2. You also need an Azure subscription with a Log Analytics workspace, which is billed by the amount of data stored.
What does ResultType 50126 mean in SigninLogs?
It means the username or password was invalid. A high count of 50126 from one IP address against many users is a common sign of password spray.
What is the difference between SigninLogs and AADNonInteractiveUserSignInLogs?
SigninLogs holds interactive sign-ins where a user enters credentials or responds to a prompt. AADNonInteractiveUserSignInLogs holds token refreshes and background sign-ins made by clients on the user's behalf, and is usually much larger.
Can these KQL queries be used in Microsoft Sentinel?
Yes. Sentinel runs on a Log Analytics workspace, so the same queries work as hunting queries or scheduled analytics rules.