Agent Foskett Academy • Microsoft Entra • Module 2 • Lesson 23

Lesson 23 — Microsoft Entra Log Analytics and KQL

Microsoft Entra logs become far more powerful when they are queried with Kusto Query Language in a Log Analytics workspace.

KQL allows administrators and security analysts to filter millions of identity events, isolate failures, group suspicious behaviour, review Conditional Access results and correlate user and workload identity activity.

This lesson introduces the Microsoft Entra Log Analytics tables, core KQL operators and a repeatable investigation workflow for sign-ins, audit events, service principals and managed identities.

A strong KQL investigation begins with a clear question, the correct table and the smallest useful set of fields.
Agent Foskett Microsoft Entra Log Analytics and KQL lesson
What you will learn

This lesson introduces the Microsoft Entra tables, KQL operators and practical identity investigation patterns used in Log Analytics and Microsoft Sentinel.

Microsoft Entra tables
Core KQL operators
Sign-in investigations
Audit and workload identity hunting

Learning objectives

After completing this lesson, you should be able to investigate Microsoft Entra activity using Log Analytics and KQL.

  • Identify the main Microsoft Entra Log Analytics tables.
  • Use core KQL operators to filter, shape and summarize identity data.
  • Investigate failed and risky user sign-ins.
  • Review audit activity and administrative changes.
  • Query service principal and managed identity authentication.
  • Build clear, reusable investigation queries.

What you need before starting

Microsoft Entra activity logs must already be routed to a Log Analytics workspace through Diagnostic Settings or the Microsoft Entra data connector for Microsoft Sentinel.

You also need permission to read the workspace and query the relevant tables.

From portal filtering to KQL investigation

The Microsoft Entra admin centre is useful for reviewing individual events. Log Analytics becomes more valuable when the investigation requires scale, repeatability, grouping or comparison across many events.

Investigation question ↓ Choose the correct Microsoft Entra table ↓ Reduce the time range and filter the evidence ↓ Project only the useful fields ↓ Summarize patterns and outliers ↓ Validate the finding against the original event

Main Microsoft Entra Log Analytics tables

TableData capturedTypical investigation
SigninLogsInteractive user sign-insFailed sign-ins, MFA, Conditional Access, device and risk analysis
AuditLogsDirectory and configuration changesRole changes, policy changes, application changes and user administration
AADNonInteractiveUserSignInLogsBackground user token activityRefresh tokens, client applications and silent authentication
AADServicePrincipalSignInLogsService principal authenticationClient secrets, certificates, app-only access and automation
AADManagedIdentitySignInLogsManaged identity authenticationAzure resource token requests and workload identity activity
ProvisioningLogsProvisioning and lifecycle operationsFailed account creation, updates, synchronisation and deprovisioning

Start with a clear question

A query should answer a specific operational question, such as:

  • Why was this user blocked?
  • Which applications generated the most failures?
  • Who changed the Conditional Access policy?
  • Which service principal used this IP address?

Start with a small time range

Time filtering reduces noise, improves performance and helps prevent unrelated events from distorting the investigation.

SigninLogs | where TimeGenerated > ago(1h)

The where operator

where filters rows that meet a condition.

SigninLogs | where TimeGenerated > ago(24h) | where UserPrincipalName =~ "alex@contoso.com"

The case-insensitive equality operator =~ is helpful when matching text values exactly.

The project operator

project selects the fields displayed in the result.

SigninLogs | where TimeGenerated > ago(24h) | project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress, ResultType, ResultDescription

The order by operator

Sort the newest or most important evidence first.

SigninLogs | where TimeGenerated > ago(24h) | order by TimeGenerated desc

The take operator

take returns a limited sample and is useful while exploring an unfamiliar table.

AADServicePrincipalSignInLogs | take 20

The distinct operator

Use distinct to identify the unique values present in a field.

SigninLogs | where TimeGenerated > ago(7d) | distinct AppDisplayName | order by AppDisplayName asc

The summarize operator

summarize groups events and calculates totals.

SigninLogs | where TimeGenerated > ago(24h) | summarize SignInCount = count() by UserPrincipalName | order by SignInCount desc

The extend operator

extend creates a calculated field without removing the original columns.

SigninLogs | where TimeGenerated > ago(24h) | extend SignInOutcome = iff(ResultType == "0", "Success", "Failure") | project TimeGenerated, UserPrincipalName, AppDisplayName, SignInOutcome

Understand result codes

For many sign-in investigations, ResultType contains the sign-in error code and a value of 0 usually represents success. ResultDescription provides a readable explanation.

SigninLogs | where TimeGenerated > ago(24h) | where ResultType != "0" | summarize Failures = count() by ResultType, ResultDescription | order by Failures desc

Always validate the event details because interrupted authentication and application-side failures can require additional context.

Latest user sign-ins

SigninLogs | where TimeGenerated > ago(1h) | project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress, ConditionalAccessStatus, ResultType | order by TimeGenerated desc

Failed user sign-ins

SigninLogs | where TimeGenerated > ago(24h) | where ResultType != "0" | project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress, ResultType, ResultDescription | order by TimeGenerated desc

Failures by user

SigninLogs | where TimeGenerated > ago(24h) | where ResultType != "0" | summarize FailureCount = count(), Applications = make_set(AppDisplayName, 10), IPAddresses = make_set(IPAddress, 10) by UserPrincipalName | order by FailureCount desc

Failures by application

SigninLogs | where TimeGenerated > ago(24h) | where ResultType != "0" | summarize FailureCount = count() by AppDisplayName | order by FailureCount desc

Conditional Access results

SigninLogs | where TimeGenerated > ago(24h) | summarize SignIns = count() by ConditionalAccessStatus | order by SignIns desc

Open the original sign-in event when policy-level evaluation details are needed.

Risky sign-ins

SigninLogs | where TimeGenerated > ago(7d) | where RiskLevelDuringSignIn !in ("none", "hidden") | project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress, RiskLevelDuringSignIn, RiskState, RiskDetail | order by TimeGenerated desc

Sign-ins from new countries

SigninLogs | where TimeGenerated > ago(7d) | extend Country = tostring(LocationDetails.countryOrRegion) | summarize SignIns = count(), Users = dcount(UserPrincipalName) by Country | order by SignIns desc

Device information

Device details are stored in a dynamic field and can be extracted with tostring().

SigninLogs | where TimeGenerated > ago(24h) | extend DeviceId = tostring(DeviceDetail.deviceId), OperatingSystem = tostring(DeviceDetail.operatingSystem), Browser = tostring(DeviceDetail.browser), IsCompliant = tostring(DeviceDetail.isCompliant) | project TimeGenerated, UserPrincipalName, AppDisplayName, DeviceId, OperatingSystem, Browser, IsCompliant

Authentication requirement

SigninLogs | where TimeGenerated > ago(24h) | summarize SignIns = count() by AuthenticationRequirement | order by SignIns desc

This provides a useful overview, but detailed MFA troubleshooting may require the AuthenticationDetails field and the original event.

Audit log investigations

AuditLogs records changes made to Microsoft Entra objects and configuration.

AuditLogs | where TimeGenerated > ago(24h) | project TimeGenerated, OperationName, Category, Result, ResultReason, InitiatedBy, TargetResources | order by TimeGenerated desc

Failed audit operations

AuditLogs | where TimeGenerated > ago(7d) | where Result !~ "success" | summarize Failures = count() by OperationName, Result, ResultReason | order by Failures desc

Search for policy changes

AuditLogs | where TimeGenerated > ago(7d) | where OperationName has_any ("policy", "Conditional Access") | project TimeGenerated, OperationName, Result, InitiatedBy, TargetResources | order by TimeGenerated desc

Service principal sign-ins

AADServicePrincipalSignInLogs | where TimeGenerated > ago(24h) | project TimeGenerated, ServicePrincipalName, AppId, ResourceDisplayName, IPAddress, ResultType, ResultDescription, CorrelationId | order by TimeGenerated desc

Service principal failures

AADServicePrincipalSignInLogs | where TimeGenerated > ago(24h) | where ResultType != "0" | summarize Failures = count(), Resources = make_set(ResourceDisplayName, 10), IPAddresses = make_set(IPAddress, 10) by ServicePrincipalName, AppId | order by Failures desc

Managed identity activity

AADManagedIdentitySignInLogs | where TimeGenerated > ago(24h) | project TimeGenerated, ServicePrincipalName, AppId, ResourceDisplayName, IPAddress, ResultType, ResultDescription, CorrelationId | order by TimeGenerated desc

Non-interactive user sign-ins

AADNonInteractiveUserSignInLogs | where TimeGenerated > ago(24h) | where UserPrincipalName =~ "alex@contoso.com" | project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress, IncomingTokenType, ResultType, ResultDescription | order by TimeGenerated desc

Agent Foskett investigation: “The user could not sign in yesterday”

  1. Define the user, approximate time and application involved.
  2. Query SigninLogs for the user and a narrow time range.
  3. Sort events chronologically and review ResultType and ResultDescription.
  4. Compare the application, IP address, device and location fields.
  5. Review ConditionalAccessStatus, authentication requirement and risk fields.
  6. Use the correlation ID to match portal details or application logs when necessary.
  7. Check AADNonInteractiveUserSignInLogs if the problem occurred during background token renewal.
  8. Confirm the root cause against the original sign-in event before documenting the finding.
let User = "alex@contoso.com"; let StartTime = ago(2d); SigninLogs | where TimeGenerated > StartTime | where UserPrincipalName =~ User | project TimeGenerated, AppDisplayName, IPAddress, ResultType, ResultDescription, ConditionalAccessStatus, AuthenticationRequirement, RiskLevelDuringSignIn, CorrelationId | order by TimeGenerated asc
Finding:  The evidence showed successful primary authentication followed by a Conditional Access interruption that required an authentication method the user had not registered.

Build reusable queries with let

The let statement keeps important values at the top of the query.

let TargetUser = "alex@contoso.com"; let InvestigationWindow = 24h; SigninLogs | where TimeGenerated > ago(InvestigationWindow) | where UserPrincipalName =~ TargetUser | order by TimeGenerated desc

Reduce noise early

Filter by time, user, application or result near the beginning of the query. Project only the fields needed to answer the question.

This makes the query easier to read and usually more efficient.

Do not assume every table has identical fields

Interactive, non-interactive, service principal and managed identity tables have related but different schemas.

Use take 10, the schema pane or Microsoft table reference before reusing a query across tables.

Know when to return to the portal

KQL is excellent for finding patterns and isolating events. The Microsoft Entra sign-in details experience may still provide the clearest policy evaluation, authentication step and diagnostic explanation for a single event.

Common KQL mistakes

  • Querying the wrong sign-in table.
  • Using an excessively large time range.
  • Assuming every non-zero result is malicious.
  • Ignoring background and workload identity activity.
  • Displaying every column instead of projecting useful evidence.
  • Failing to validate summarized findings against raw events.

Investigation checklist

  • State the question before writing the query.
  • Choose the table that represents the identity type.
  • Limit the time range.
  • Filter early.
  • Project useful fields.
  • Summarize only after reviewing raw events.
  • Preserve correlation IDs and timestamps.
  • Document the final evidence and conclusion.

Security best practices

  • Save validated investigation queries in a controlled repository.
  • Use meaningful query names and comments.
  • Protect access to the Log Analytics workspace.
  • Review ingestion gaps before relying on a negative result.
  • Monitor both human and workload identities.
  • Convert repeatable high-value hunts into analytics or scheduled detections where appropriate.

Key takeaways

  • Log Analytics provides scalable investigation of Microsoft Entra telemetry using KQL.
  • The correct table depends on whether the identity is a user, service principal or managed identity.
  • where, project, summarize, extend, order by, take and distinct form the core of many investigations.
  • Result codes, Conditional Access, device, authentication, location and risk fields provide essential sign-in context.
  • AuditLogs records directory and configuration changes.
  • Workload identity tables must be queried separately from user sign-ins.
  • Good KQL starts with a clear question and ends with validation against the raw evidence.

Continue learning

Continue through Microsoft Entra monitoring and identity investigation, or return to the academy roadmap.

Microsoft Entra Log Analytics and KQL investigations

Microsoft Entra activity logs can be queried in Log Analytics using KQL across SigninLogs, AuditLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs, AADManagedIdentitySignInLogs and ProvisioningLogs.

Microsoft Entra Academy Lesson 23 — Log Analytics and KQL

This Agent Foskett lesson teaches practical KQL operators, failed sign-in analysis, Conditional Access investigation, audit log review and workload identity hunting.