Auditing Microsoft Entra Authentication Methods with Microsoft Sentinel and Azure Logic Apps
A practical guide to collecting and analysing user authentication method data with Microsoft Graph, Azure Logic Apps, and Microsoft Sentinel.

Overview.
When managing identity security in Microsoft Entra ID, having clear visibility into which multi-factor authentication (MFA) methods users have registered is essential.
It underpins compliance, user assurance, and incident response.
Entra ID’s portal presents a convenient, current-state view of a user’s authentication methods. It is useful for spot checks but it is not designed for tenant-wide assurance, forensic context, or durable compliance evidence.
Microsoft Sentinel’s native identity tables focus on events such as sign-ins and administrative changes, not on the underlying configuration state of which methods each user has registered. The authoritative source for that configuration is Microsoft Graph’s authentication methods API.
The most reliable pattern is to collect from Graph on a schedule, normalise the results, and write into a custom Sentinel table that you can query with KQL for audits and investigations.
Microsoft Sentinel can ingest valuable identity telemetry, SignInLogs, AuditLogs, and risk detections, but these sources do not reveal which MFA methods each account has configured.
Why You Need Real MFA Visibility.
Having visibility of which authentication methods users have registered in Microsoft Entra ID is invaluable for identity assurance, compliance, and operational monitoring. It is not enough to know that multi-factor authentication (MFA) is enforced. You need to know which authentication methods each user has actually registered.
Entra’s native tools, such as the Authentication Methods dashboard and the IdentityInfo table in Microsoft Sentinel give only a partial picture. The IdentityInfo table provides a Boolean flag (“registered / not registered”), and while AuditLogs can record when an MFA method is added or removed, neither provides the full configuration-level detail of which methods are actually present on each account.
That limitation makes it impossible to answer questions such as:
Which users still rely solely on passwords?
How many FIDO2 keys or Authenticator apps are deployed?
Are any users using outdated or duplicate devices?
Can we prove our MFA rollout for audit or compliance evidence?
To obtain a complete, historical, and queryable record of authentication methods, we must pull data directly from the Microsoft Graph API and store it where it can be queried and correlated: Microsoft Sentinel.
Where Entra Falls Short.
Native UI: Authentication Methods Reporting
The User Registration Details view under Identity → Authentication methods in the Entra Admin Centre provides useful at-a-glance information but is limited:
It displays only current state, with no history.
Phone numbers and device names are masked for privacy.
There is no export or filtering beyond basic search.
It cannot answer questions like “Which users have registered both Authenticator and FIDO2?”
Sentinel’s Built-in Tables
When diagnostic settings are connected, Sentinel receives the following data streams from Entra ID:
| Table | Example Content | Limitation |
| SignInLogs | MFA required / satisfied, Conditional Access results | Event data only — no configuration. |
| AuditLogs | Method added or removed, policy changes | Captures change events, not current state. |
| IdentityInfo | MFA registered = true/false | Lacks method-level detail. |
These tables are designed for behavioural analysis, not configuration auditing. To audit MFA properly, you need to extract configuration from the source of truth, Microsoft Graph.
The Authoritative Source: Microsoft Graph.
The Endpoint.
The Microsoft Graph endpoint /users/{id}/authentication/methods returns the full list of authentication methods registered for any given user:
GET https://graph.microsoft.com/v1.0/users/{id or UPN}/authentication/methods
Example output:
{
"value": [
{
"@odata.type": "#microsoft.graph.passwordAuthenticationMethod"
},
{
"@odata.type": "#microsoft.graph.microsoftAuthenticatorAuthenticationMethod",
"displayName": "iPhone 17 Pro",
"deviceTag": "SoftwareTokenActivated",
"phoneAppVersion": "6.8.30"
},
{
"@odata.type": "#microsoft.graph.fido2AuthenticationMethod",
"model": "YubiKey 5 NFC"
}
]
}
Each @odata.type identifies the authentication method type:
| Method Type | Description |
#microsoft.graph.passwordAuthenticationMethod | Password credential. |
#microsoft.graph.microsoftAuthenticatorAuthenticationMethod | Microsoft Authenticator app registration. |
#microsoft.graph.windowsHelloForBusinessAuthenticationMethod | Windows Hello for Business key. |
#microsoft.graph.phoneAuthenticationMethod | Phone number used for SMS or voice MFA. |
#microsoft.graph.fido2AuthenticationMethod | FIDO2 hardware key (e.g. YubiKey, Feitian). |
This data is far richer than anything available in the Entra GUI or tables and is the same source that Entra itself uses internally.
NOTE: The
#microsoft.graph.prefix comes directly from the@odata.typefield in the Microsoft Graph API response. It represents the fully qualified object type within the Graph schema (namespacemicrosoft.graph).For readability, use the simplified form (for example,
fido2AuthenticationMethodinstead of#microsoft.graph.fido2AuthenticationMethod), but the full prefix is what you’ll see in the raw API data and Sentinel logs.
Solution Overview.
The goal is to automate collection of authentication method data using Azure Logic Apps and Microsoft Graph, then ingest the results into a custom Sentinel table (for example UserAuthMethods_CL).
This gives you a historical, queryable record of MFA configurations across your tenant, perfect for compliance reporting, audit evidence, or security investigations.
Examples.
Generate a readable MFA configuration summary per user:
userAuthMethods_CL
| extend UserPrincipalName = url_decode(extract(@"users\('(.+)'\)/authentication/", 1, _odata_context_s))
| mv-expand Auth = todynamic(value_s)
| extend MethodType = replace(@"#microsoft\.graph\.", "", tostring(Auth['@odata.type']))
| extend DeviceName = tostring(Auth.displayName)
| extend PhoneNumber = tostring(Auth.phoneNumber)
| extend DeviceTag = tostring(Auth.deviceTag)
| extend AppVersion = tostring(Auth.phoneAppVersion)
| extend Created = todatetime(Auth.createdDateTime)
| extend MethodDetail = case(
MethodType == "microsoftAuthenticatorAuthenticationMethod", strcat("Authenticator: ", DeviceName, " (", DeviceTag, ", v", AppVersion, ")"),
MethodType == "phoneAuthenticationMethod", strcat("Phone: ", PhoneNumber),
MethodType == "windowsHelloForBusinessAuthenticationMethod", strcat("Windows Hello: ", DeviceName),
MethodType == "fido2AuthenticationMethod", strcat("FIDO2 Key: ", DeviceName),
MethodType == "passwordAuthenticationMethod", "Password only",
strcat(MethodType, ": ", coalesce(DeviceName, PhoneNumber, DeviceTag))
)
| summarize AuthSummary = strcat_array(make_set(MethodDetail), " | ") by UserPrincipalName
| project UserPrincipalName, AuthSummary
| order by UserPrincipalName asc
| where UserPrincipalName contains "me"

Summarize the number of users with each type of authentication method:
userAuthMethods_CL
| extend
username=url_decode(extract(@"users\('(.+)'\)/authentication/", 1, _odata_context_s))
| summarize arg_max(TimeGenerated, *) by username
| mv-expand todynamic(value_s)
| extend method=tostring(value_s["@odata.type"])
| summarize count() by method

Enrich Phone-Based MFA Records with Country Information.
let dialingCodes=externaldata(callingCode:string, countryName:string)
[@'https://raw.githubusercontent.com/karani-gk/Country-Calling-Codes/refs/heads/main/Countries.csv'] with(format="csv");
userAuthMethods_CL
| extend
username=url_decode(extract(@"users\('(.+)'\)/authentication/", 1, _odata_context_s))
| summarize arg_max(TimeGenerated, *) by username
| mv-expand todynamic(value_s)
| extend method=tostring(value_s["@odata.type"])
| where method == "#microsoft.graph.phoneAuthenticationMethod"
| extend phoneNumber_ = tostring(value_s.phoneNumber)
| extend countryCode=substring(phoneNumber_, 0, 3)
| lookup (dialingCodes) on $left.countryCode==$right.callingCode

Scraping The Data.
The Microsoft Graph API exposes an endpoint that provides detailed information about each user’s registered authentication methods, exactly the data we need.
The Azure Logic App in this implementation automates that process by:
Retrieving a complete list of users from the tenant.
Querying the Microsoft Graph API for each user’s
/authentication/methodsendpoint.Forwarding the collected data into a custom Log Analytics table (
userAuthMethods_CL) within Microsoft Sentinel for analysis and compliance reporting.
To allow the Logic App to query Microsoft Graph, it must be granted permission to access directory data. There are two ways to achieve this:
System-assigned managed identity:
The Logic App can authenticate to Microsoft Graph using a managed identity, but this approach requires assigning high-privilege Entra ID roles (such as Directory Reader or Reports Reader).App registration with OAuth:
A more flexible and secure method is to create a dedicated Azure AD app registration and configure the Logic App to authenticate via OAuth 2.0.
The app registration only requires the following Microsoft Graph API permissions:
Designing The Collection Pipeline.
To continuously capture this data and make it available for analysis, you can automate the process using Azure Logic Apps.
A Logic App executes the following sequence every 24 hours:
List all users in the tenant using the
/usersGraph endpoint.Query each user’s authentication methods using
/users/{id}/authentication/methods.Send the results to Sentinel through the Azure Log Analytics Data Collector connector.
This creates a custom table, userAuthMethods_CL, in your Sentinel workspace containing timestamped records of each user’s registered MFA methods.
Once the data is in Sentinel, you can query, summarise, or alert on MFA coverage with KQL.
Step 1/3: Enterprise App Registration.
To authenticate against Microsoft Graph securely without assigning broad system identities:
In the Entra ID admin center, navigate to App registrations.
Select New registration.
Name the application.
Choose
Accounts in this organizational directory only (Single tenant)Select Register.
Select API Permissions → Add a permission → Microsoft Graph → Application permissions.
Add the following permissions:
Choose Grant consent for your tenant.
Navigate to Certificates & Secrets → New client secret.
Choose an expiration date (The recommendation is 180 days/6 months) → Add.
Make a note of the client secret
ValueandSecret IDvalues, as you will need these later.
Step 2/3: Creating The Azure Logic App.
In the Azure portal, search Deploy a custom template.
Choose Build your own template in the editor.
Paste in the below deployment template → Save.
Deployment Template:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"workflows_Get_Entra_AuthMethods_Logic_App_name": {
"defaultValue": "Get-Entra-AuthMethods-Logic-App",
"type": "String"
},
"connections_azureloganalyticsdatacollector_1_externalid": {
"defaultValue": "/subscriptions/fb47e429-e603-41ff-86a1-36b85336ac4a/resourceGroups/soc-cdoherty-prod-rg/providers/Microsoft.Web/connections/azureloganalyticsdatacollector-1",
"type": "String"
}
},
"variables": {},
"resources": [
{
"type": "Microsoft.Logic/workflows",
"apiVersion": "2017-07-01",
"name": "[parameters('workflows_Get_Entra_AuthMethods_Logic_App_name')]",
"location": "uksouth",
"identity": {
"type": "SystemAssigned"
},
"properties": {
"state": "Enabled",
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"$connections": {
"defaultValue": {},
"type": "Object"
}
},
"triggers": {
"Recurrence": {
"recurrence": {
"interval": 24,
"frequency": "Hour",
"timeZone": "GMT Standard Time"
},
"evaluatedRecurrence": {
"interval": 24,
"frequency": "Hour",
"timeZone": "GMT Standard Time"
},
"type": "Recurrence"
}
},
"actions": {
"Get_all_users_from_Graph_API": {
"runAfter": {},
"type": "Http",
"inputs": {
"uri": "https://graph.microsoft.com/v1.0/users",
"method": "GET",
"authentication": {
"type": "ActiveDirectoryOAuth",
"authority": "https://login.microsoftonline.com",
"tenant": "fd270158-ff15-46c6-b89c-8c989a167ade",
"audience": "https://graph.microsoft.com",
"clientId": "4169c87a-e141-4a23-8b3a-f3e7c54fd124",
"secret": "tJe8Q~3Z4edikz8u3wAde1TYezB2OxIDQe_w-b.2"
}
}
},
"Parse_Users_JSON": {
"runAfter": {
"Get_all_users_from_Graph_API": [
"Succeeded"
]
},
"type": "ParseJson",
"inputs": {
"content": "@body('Get_all_users_from_Graph_API')",
"schema": {
"type": "object",
"properties": {
"@@odata.context": {
"type": "string"
},
"value": {
"type": "array",
"items": {
"type": "object",
"properties": {
"businessPhones": {
"type": "array"
},
"displayName": {
"type": "string"
},
"givenName": {},
"jobTitle": {},
"mail": {
"type": [
"string",
"null"
]
},
"mobilePhone": {},
"officeLocation": {},
"preferredLanguage": {},
"surname": {},
"userPrincipalName": {
"type": "string"
},
"id": {
"type": "string"
}
},
"required": [
"userPrincipalName",
"id"
]
}
}
}
}
}
},
"For_each_User": {
"foreach": "@outputs('Parse_Users_JSON')?['body']?['value']",
"actions": {
"If_user_is_not_external": {
"actions": {
"Get_User_Auth_Methods": {
"type": "Http",
"inputs": {
"uri": "https://graph.microsoft.com/v1.0/users/@{item()?['userPrincipalName']}/authentication/methods",
"method": "GET",
"authentication": {
"type": "ActiveDirectoryOAuth",
"authority": "https://login.microsoftonline.com",
"tenant": "PutYourTenantIDHere",
"audience": "https://graph.microsoft.com",
"clientId": "PutYourClientIDHere",
"secret": "PutYourClientSecretHere"
}
}
},
"Send_Data_to_Sentinel": {
"runAfter": {
"Get_User_Auth_Methods": [
"Succeeded"
]
},
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['azureloganalyticsdatacollector-1']['connectionId']"
}
},
"method": "post",
"body": "@{body('Get_User_Auth_Methods')}",
"headers": {
"Log-Type": "userAuthMethods"
},
"path": "/api/logs"
}
}
},
"else": {
"actions": {}
},
"expression": {
"and": [
{
"not": {
"contains": [
"@item()?['userPrincipalName']",
"#EXT#"
]
}
}
]
},
"type": "If"
}
},
"runAfter": {
"Parse_Users_JSON": [
"Succeeded"
]
},
"type": "Foreach"
}
},
"outputs": {}
},
"parameters": {
"$connections": {
"type": "Object",
"value": {
"azureloganalyticsdatacollector-1": {
"id": "/subscriptions/fb47e429-e603-41ff-86a1-36b85336ac4a/providers/Microsoft.Web/locations/uksouth/managedApis/azureloganalyticsdatacollector",
"connectionId": "[parameters('connections_azureloganalyticsdatacollector_1_externalid')]",
"connectionName": "azureloganalyticsdatacollector-1"
}
}
}
}
}
}
]
}
Azure Logic App Design & Flow.
Below is the production Logic App definition used for this project.
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"contentVersion": "1.0.0.0",
"triggers": {
"Recurrence": {
"type": "Recurrence",
"recurrence": {
"interval": 24,
"frequency": "Hour",
"timeZone": "GMT Standard Time"
}
}
},
"actions": {
"Get_all_users_from_Graph_API": {
"type": "Http",
"inputs": {
"uri": "https://graph.microsoft.com/v1.0/users",
"method": "GET",
"authentication": {
"type": "ActiveDirectoryOAuth",
"authority": "https://login.microsoftonline.com",
"tenant": "<tenant-id>",
"audience": "https://graph.microsoft.com",
"clientId": "<client-id>",
"secret": "<client-secret>"
}
}
},
"Parse_Users_JSON": {
"type": "ParseJson",
"inputs": {
"content": "@body('Get_all_users_from_Graph_API')",
"schema": {
"type": "object",
"properties": {
"value": {
"type": "array",
"items": {
"type": "object",
"properties": {
"userPrincipalName": { "type": "string" },
"id": { "type": "string" }
},
"required": [ "userPrincipalName", "id" ]
}
}
}
}
},
"runAfter": { "Get_all_users_from_Graph_API": [ "Succeeded" ] }
},
"For_each_User": {
"type": "Foreach",
"foreach": "@outputs('Parse_Users_JSON')?['body']?['value']",
"actions": {
"If_user_is_not_external": {
"type": "If",
"expression": {
"not": {
"contains": [
"@item()?['userPrincipalName']",
"#EXT#"
]
}
},
"actions": {
"Get_User_Auth_Methods": {
"type": "Http",
"inputs": {
"uri": "https://graph.microsoft.com/v1.0/users/@{item()?['userPrincipalName']}/authentication/methods",
"method": "GET",
"authentication": "@parameters('GraphOAuth')"
}
},
"Send_Data_to_Sentinel": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['azureloganalyticsdatacollector-1']['connectionId']"
}
},
"method": "post",
"body": "@{body('Get_User_Auth_Methods')}",
"headers": { "Log-Type": "userAuthMethods" },
"path": "/api/logs"
},
"runAfter": { "Get_User_Auth_Methods": [ "Succeeded" ] }
}
}
}
},
"runAfter": { "Parse_Users_JSON": [ "Succeeded" ] }
}
}
}
}
Querying Data in Sentinel.
Once ingested, your data lives in userAuthMethods_CL and can be queried using KQL.
Inventory of FIDO2 models:
userAuthMethods_CL
| mv-expand todynamic(value_s)
| where value_s["@odata.type"] == "#microsoft.graph.fido2AuthenticationMethod"
| summarize count() by Model = tostring(value_s.model)
Summarise users by authentication method:
userAuthMethods_CL
| mv-expand todynamic(value_s)
| extend Method = tostring(value_s["@odata.type"])
| summarize Users = count() by Method
| order by Users desc
Benefits of the Logic App Approach.
| Feature | Entra Portal | AuditLogs / IdentityInfo | Azure Logic App + Graph |
| Detail | Masked summary | Event-based only | Full, unmasked data |
| Historical Tracking | None | Partial (events only) | Continuous timeline |
| Scalability | Manual | Limited | Automated, scalable |
| Integration | GUI only | Internal | Sentinel-native |
| Security | Role-bound | Internal | App-scoped Graph permissions |
This approach provides a complete and auditable dataset suitable for SOC analytics, MFA coverage validation, and compliance evidence for standards such as ISO 27001, Cyber Essentials Plus, and NCSC Cloud Security Principles.
Final Thoughts.
Native Entra reporting is management-level, useful for checking individual users but not for enterprise-scale auditing. By querying Graph through an automated Logic App and storing the data in Microsoft Sentinel, you create a long-term, analytical record of MFA registrations across your organisation.
This approach turns transient configuration data into an auditable evidence trail, exactly what compliance frameworks and SOC teams need to demonstrate effective MFA enforcement.




