Entra ID sign-in gap analysis with KQL

Four KQL queries over the Entra sign-in logs that surface legacy authentication, single-factor sign-ins, sign-ins no Conditional Access policy applied to, and which MFA methods are really in use.

The problem

Conditional Access and MFA policies describe the intended state. The sign-in logs show the actual one: old protocols still in use, accounts signing in with a password only, applications that slip past every policy.

Approach

Send the Entra sign-in logs to a Log Analytics workspace (or Microsoft Sentinel) with diagnostic settings, then query SigninLogs. Each query answers one question and produces a short list you can act on.

Implementation

These queries run against the SigninLogs table in Log Analytics or Microsoft Sentinel. The table only exists once Diagnostic settings in the Entra admin center send SignInLogs to the workspace.

Legacy authentication still in use

Modern clients report as Browser or Mobile Apps and Desktop clients. Everything else is a legacy protocol that cannot do MFA.

SigninLogs
| where TimeGenerated > ago(30d)
| where isnotempty(ClientAppUsed)
| where ClientAppUsed !in ("Browser", "Mobile Apps and Desktop clients")
| summarize SignIns = count(), Apps = make_set(AppDisplayName, 10) by UserPrincipalName, ClientAppUsed
| sort by SignIns desc

Successful single-factor sign-ins

Accounts that signed in with only one factor. Some will be expected (trusted locations, service scenarios); the rest are the ones to look at first.

SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType == "0" and AuthenticationRequirement == "singleFactorAuthentication"
| extend Country = tostring(LocationDetails.countryOrRegion)
| summarize SignIns = count(), Apps = make_set(AppDisplayName, 10), Countries = make_set(Country, 10) by UserPrincipalName
| sort by SignIns desc

Sign-ins no Conditional Access policy applied to

A successful sign-in with ConditionalAccessStatus of notApplied was not evaluated by any enabled policy.

SigninLogs
| where TimeGenerated > ago(7d)
| where ResultType == "0" and ConditionalAccessStatus == "notApplied"
| summarize SignIns = count(), Users = dcount(UserPrincipalName) by AppDisplayName
| sort by SignIns desc

Which authentication methods are really used

Useful before moving to phishing-resistant MFA: it shows how many users still rely on push notifications, SMS or voice.

SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType == "0"
| mv-expand AuthDetail = parse_json(AuthenticationDetails)
| extend Method = tostring(AuthDetail.authenticationMethod)
| where isnotempty(Method) and Method != "Previously satisfied"
| summarize Users = dcount(UserPrincipalName), SignIns = count() by Method
| sort by Users desc

Licensing: sending sign-in logs to Log Analytics requires Microsoft Entra ID P1 or P2, and the workspace is billed by ingested data volume.

Security considerations

Sign-in logs hold personal data such as user names, IP addresses and locations, so access to the workspace should follow least privilege and your retention rules. The queries are read-only. The results show where policies have gaps, so share them only with the people who will close those gaps.