๐ Recipe ยท Entra ID & Identity
Find a User's Last Sign-In Activity via Microsoft Graph (without Entra ID P1)
Retrieve the most recent sign-in event for a user from audit logs when the tenant lacks Entra ID Premium licensing
Complexity
Intermediate
Impact
security + audit + identity + troubleshooting
Context
Why This Matters
Administrators frequently need to confirm when a user last signed in โ to investigate account compromise, validate whether a departing employee has been active, audit dormant accounts, or triage login complaints. The obvious answer is the signInActivity property on the user object in Microsoft Graph, but that property is gated behind an Entra ID Premium P1 or P2 license. Tenants on Microsoft 365 Business Basic/Standard/Premium (without the add-on) or Office 365 E1/E3 will receive an HTTP 403 โ Tenant is not a B2C tenant and doesn't have premium license error when querying it.
The good news: the auditLogs/signIns endpoint is available in all tenants and retains up to 30 days of sign-in events (7 days for tenants without Premium). This recipe shows how to fall back to audit logs to answer the same question, and how to interpret the result โ including common failure codes like 50057 (account disabled).
Run this recipe when: you need to know a user's last login, you're investigating a help-desk ticket ("why can't I sign in?"), or you're auditing account activity on a non-Premium tenant.
Expected Outcomes
- The user's most recent sign-in event (successful or failed) with timestamp, application, IP address, and location.
- The sign-in result โ including any failure reason and error code โ so you know whether the user actually got in.
- A reusable pattern for querying sign-in logs by user ID that works on any tenant, regardless of license tier.
- Clear next-step guidance (e.g., account is disabled โ offer to re-enable; repeated failures from unknown IP โ consider revoke sessions + password reset).
Risks & Considerations
Retention limits
The auditLogs/signIns endpoint only retains 30 days of data on Premium tenants and just 7 days on non-Premium tenants. If the user hasn't signed in recently, the log may be empty โ this does not mean they never signed in, just that it was outside the retention window.
Failed sign-ins are still sign-ins
The most recent event may be a failed attempt (disabled account, wrong password, MFA failure). Always check the status.errorCode and status.failureReason fields before concluding the user was active. A non-zero errorCode means the sign-in did not succeed.
Non-interactive sign-ins
The default signIns endpoint returns interactive sign-ins. Service principal sign-ins, token refreshes, and some background authentications appear in separate collections (nonInteractiveUserSignIns, servicePrincipalSignIns). For a complete picture, query those too.
Privacy and compliance
Sign-in logs contain IP addresses and geolocation โ treat this as personal data under GDPR/CCPA. Do not export or forward it outside approved IT channels without a documented reason.
Don't confuse this with mailbox activity
A user with no recent sign-ins may still be receiving email, calendar invites, etc. If you're evaluating whether an account is truly dormant, also check mailbox last-activity via the Exchange reports or Graph usage reports.
Required Permissions
| Permission | Why It's Needed |
|---|---|
| AuditLog.Read.All | Required to read sign-in events from /auditLogs/signIns |
| Directory.Read.All | Required to resolve a display name or UPN to a user object ID before querying logs |
| User.Read.All | Alternative to Directory.Read.All for looking up the user record |
The fastest way to get this done: just ask Dex. Copy the prompt below and paste it into your Dex conversation.
For IT Admins
Paste into Dex Pro
Look up last sign-in via the Entra admin center
Even on non-Premium tenants, the sign-in logs UI is available (with reduced retention). This is the fastest path for a one-off lookup.
Step 1 โ Open the Entra admin center
- Sign in to https://entra.microsoft.com as a Global Reader, Security Reader, or Global Administrator.
- Navigate to .
Step 2 โ Find the user
- In the search box, type the user's display name or UPN (e.g.,
Adrian Blackmonoradrian@contoso.com). - Click the user's name to open their profile.
- Note the Account enabled status at the top โ a disabled account explains why recent sign-ins may be failing.
Step 3 โ View sign-in logs for this user
- In the user's profile, select from the left menu (under Activity).
- The default view shows interactive sign-ins from the last 24 hours. Change the Date filter to Last 7 days or Last 30 days (30 days requires Premium).
- Sort by Date descending. The top row is the most recent sign-in attempt.
Step 4 โ Inspect the event
- Click the top row to open the sign-in details panel.
- Review: Date, Status (Success / Failure), Application, IP address, Location, and if failed, the Sign-in error code and Failure reason.
- Switch between the Interactive, Non-interactive, Service principal, and Managed identity tabs if you need a full picture.
Step 5 โ Export if needed
Use Download > CSV at the top of the logs grid to export the filtered results for a ticket or audit record.
Query sign-in logs directly via Microsoft Graph
This two-call pattern works on any tenant โ it does not require Entra ID Premium.
Step 1 โ Resolve the user to an object ID
GET https://graph.microsoft.com/v1.0/users?$filter=startswith(displayName,'{displayName}')&$select=id,displayName,userPrincipalName,accountEnabledExample response:
{
"value": [
{
"id": "6ec10ce2-e41b-46cb-bda2-2900a8965720",
"displayName": "Adrian Blackmon",
"userPrincipalName": "adrian@contoso.com",
"accountEnabled": false
}
]
}Capture id โ you'll use it as the userId filter in the next call.
Step 2 โ Attempt signInActivity (optional, Premium only)
If your tenant does have Entra ID P1/P2, the single-call path is:
GET https://graph.microsoft.com/v1.0/users/{userId}?$select=signInActivityOn non-Premium tenants this returns HTTP 403 with message "Tenant is not a B2C tenant and doesn't have premium license". That is your cue to fall back to Step 3.
Step 3 โ Query the sign-in audit log
GET https://graph.microsoft.com/v1.0/auditLogs/signIns?$filter=userId eq '{userId}'&$orderby=createdDateTime desc&$top=1&$select=createdDateTime,userDisplayName,userPrincipalName,appDisplayName,status,ipAddress,locationExample response:
{
"value": [
{
"createdDateTime": "2026-03-31T13:52:12Z",
"userDisplayName": "Adrian Blackmon",
"userPrincipalName": "adrian@contoso.com",
"appDisplayName": "Microsoft Authentication Broker",
"ipAddress": "2607:fb91:...",
"status": {
"errorCode": 50057,
"failureReason": "The user account is disabled."
},
"location": { "city": "Austin", "state": "Texas", "countryOrRegion": "US" }
}
]
}Interpreting the result
status.errorCode == 0โ successful sign-in.status.errorCode != 0โ failed sign-in. Common codes:50057(disabled),50126(invalid credentials),50074(MFA required),50053(account locked).- Empty
valuearray โ no sign-ins within the retention window (7 or 30 days).
Useful variants
- Last 10 events: change
$top=1to$top=10. - Only successful sign-ins: add
and status/errorCode eq 0to the filter. - Non-interactive (token refreshes, background auth): query
/auditLogs/signIns?$filter=userId eq '{userId}' and signInEventTypes/any(t:t eq 'nonInteractiveUser').
Automate last-sign-in lookups with PowerShell
The Microsoft.Graph PowerShell SDK wraps both Graph calls shown above. This is the cleanest option for repeated lookups or embedding in a help-desk workflow.
Step 1 โ Install and connect
# One-time install
Install-Module Microsoft.Graph -Scope CurrentUser
# Connect with the scopes needed for user lookup + sign-in log reads
Connect-MgGraph -Scopes "User.Read.All", "AuditLog.Read.All"Step 2 โ Resolve the user
$user = Get-MgUser -Filter "userPrincipalName eq 'adrian@contoso.com'" -Property Id,DisplayName,UserPrincipalName,AccountEnabled
$userStep 3 โ Query sign-in logs
$lastSignIn = Get-MgAuditLogSignIn
-Filter "userId eq '$($user.Id)'"
-Top 1
-Sort "createdDateTime desc"
$lastSignIn | Select-Object CreatedDateTime, AppDisplayName, IpAddress,
@{n='City';e={$_.Location.City}},
@{n='Result';e={if($_.Status.ErrorCode -eq 0){'Success'}else{"Failed ($($_.Status.ErrorCode): $($_.Status.FailureReason))"}}}Step 4 โ Handle the Premium-only path
If you're on a Premium tenant, Get-MgUser -Property SignInActivity returns the same data in a single call. Wrap it in try/catch and fall back to Get-MgAuditLogSignIn on 403 errors โ that's exactly what the downloadable script below does.
Next steps