To find failed sign-ins in Microsoft Entra ID, read the sign-in logs and keep the entries whose error code is not 0, then group them by account to see who fails most. Reading the logs through Microsoft Graph needs Microsoft Entra ID P1 or P2. Entra keeps sign-ins for 30 days with P1 or P2 and 7 days without, so a failure older than that is gone unless the logs were archived.
| Method | Best fit |
|---|---|
| PowerShell | A CSV of every failure in the days you choose, and a second of the accounts with the most. |
| Admin centre | Looking into 1 failure: its error code, its reason and the Conditional Access result. |
| Altivane | Recommended for weekly reviews, with each account's failures counted beside its usual reason. |
Before you start
Use an account with a role that can read the sign-in logs. Reports Reader is the least privileged; Global Reader, Security Reader, Security Operator and Security Administrator work too. Reading the logs with Microsoft Graph needs Microsoft Entra ID P1 or P2, while the admin centre shows them on every edition. The PowerShell example requests AuditLog.; your organisation may require administrator consent.
Microsoft Graph returns interactive user sign-ins, newest first, from within the retention period, and a sign-in typically appears within 6 hours. The examples read and export; they change nothing.
Option 1
Find failed sign-ins with PowerShell
Install the modules once, then connect and export the failed sign-ins of the last 7 days. The filter runs in Microsoft Graph, so only failures come back. Change $days to widen the window, up to the retention period.
# Install once, if the modules are not already installed.
Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Reports -Scope CurrentUser$ErrorActionPreference = 'Stop'
$days = 7
$since = (Get-Date).ToUniversalTime().AddDays(-$days).ToString('s') + 'Z'
Connect-MgGraph -Scopes 'AuditLog.Read.All' -NoWelcome
try {
$filter = "createdDateTime ge $since and status/errorCode ne 0"
$failures = @(Get-MgAuditLogSignIn -All -Filter $filter)
if ($failures.Count -eq 0) {
Write-Output "No sign-in failed in the last $days days."
} else {
$failures | Select-Object CreatedDateTime, UserDisplayName, UserPrincipalName,
AppDisplayName, ClientAppUsed, IPAddress,
@{ Name = 'ErrorCode'; Expression = { $_.Status.ErrorCode } },
@{ Name = 'FailureReason'; Expression = { $_.Status.FailureReason } },
@{ Name = 'Country'; Expression = { $_.Location.CountryOrRegion } } |
Export-Csv -Path './failed-sign-ins.csv' -NoTypeInformation -Encoding utf8
$byUser = foreach ($person in ($failures | Group-Object UserPrincipalName)) {
$latest = $person.Group | Sort-Object CreatedDateTime | Select-Object -Last 1
$reasons = @($person.Group | Group-Object { $_.Status.FailureReason } |
Sort-Object Count -Descending)
[pscustomobject]@{
UserPrincipalName = $person.Name
Failures = $person.Count
LastFailure = $latest.CreatedDateTime
CommonReason = $reasons[0].Name
}
}
$byUser | Sort-Object Failures -Descending |
Export-Csv -Path './failed-sign-ins-by-user.csv' -NoTypeInformation -Encoding utf8
Write-Output "$($failures.Count) failed sign-ins for $(@($byUser).Count) sign-in names."
}
} finally {
Disconnect-MgGraph | Out-Null
}The script writes every failure to 1 file and a second with 1 row per sign-in name, the most failures first, with its last failure and its most common reason. A sign-in name is what was typed, so a name that is no account in the tenant gets a row too. To look for 1 error, change ne 0 to eq and the code, such as eq 500121 for MFA prompts left unanswered.
Option 2
Review failed sign-ins in the admin centre
- Sign in to the Microsoft Entra admin centre as at least a Reports Reader.
- Go to Entra ID > Monitoring & health > Sign-in logs.
- Select Failure from the Status menu to show only failed sign-ins. Search by username or application to narrow the list.
- Select a failed sign-in to open its details and read the Sign-in error code and Failure reason, and the Conditional Access tab for the policies that applied.
- Close the details without changing anything.
This suits looking into 1 person's failed sign-in while they wait. The log lists 1 sign-in per row, so for who failed most across a week, use PowerShell or a report.
Option 3 ยท Recommended for weekly reviews
Report on failed sign-ins with Altivane
- Open Altivane and choose the tenant.
- Run the Failed Sign-Ins report. It reads the last 7 days and needs certificate sign-in.
- Open the tenant's Excel workbook and find Failed sign-ins by user: each sign-in name with its failures, its last failure, its most common reason, the countries and the number of IP addresses, the most failures first.
- Read the Failed sign-ins table for each failure with its app, error code, reason, client app, location and Conditional Access result.
Altivane reads the same log, so the same licence applies; without Microsoft Entra ID P1 or P2 the report says Not set up for this tenant. A sign-in name that is no account in the tenant is listed like any other, so names someone guessed sit beside real ones. A week with more than 20,000 failures is cut to the newest 20,000 and the report says it is partial. The workbook is kept, so 1 week can be compared with the next.
Contact usCommon questions
Why does the script say the tenant doesn't have a premium licence?
Reading the sign-in logs with Microsoft Graph needs Microsoft Entra ID P1 or P2. Without it, read the logs in the admin centre instead, which shows them on every edition for 7 days. If the tenant has the licence and the error comes and goes, Microsoft says to add Directory. to the scopes.
Is every failure an attack?
No. Error 50058 means the person was authenticated but did not finish signing in, and 500121 means they did not complete the MFA prompt, often because they have not set MFA up. Enter any other code in Microsoft's error code lookup tool for its meaning and the fix it suggests.
How do I find accounts that never sign in at all?
A failed sign-in is still an attempt. For accounts with none, see how to find users who have never signed in.
Microsoft references
Technical references checked on 6 November 2026. The example scripts have been reviewed and syntax checked; they have not been run against a live tenant.