PowerShell Microsoft Graph: Audit MFA Status for All Users

MSOnline Is Deprecated — Use Graph Instead
For years, Get-MsolUser from the MSOnline module was the go-to for checking MFA status. Microsoft deprecated that module, and its MFA properties were always limited — they reflected legacy per-user MFA settings, not Conditional Access policies or modern authentication methods like FIDO2 keys and Authenticator app. The Microsoft Graph authentication methods API is the correct modern approach: it returns every registered method for every user, works with Entra ID’s full method catalog, and supports the same scripting patterns as the rest of the Graph SDK.
Quick Answer
Connect with Connect-MgGraph -Scopes UserAuthenticationMethod.Read.All,User.Read.All, page through users with Get-MgUser, retrieve each user’s authentication methods with Get-MgUserAuthenticationMethod, and flag accounts where the only method is PasswordAuthenticationMethod.
Required Graph Permissions: UserAuthenticationMethod.Read.All
The authentication methods API requires the UserAuthenticationMethod.Read.All permission, plus User.Read.All to enumerate all users. For unattended scripts, register an app registration in Entra ID with these application permissions and connect with a client certificate or client secret.
# Interactive sign-in (delegate permissions)
Connect-MgGraph -Scopes 'UserAuthenticationMethod.Read.All', 'User.Read.All'
# Verify connected context
$ctx = Get-MgContext
Write-Host "Connected as: $($ctx.Account)"
Write-Host "Scopes : $($ctx.Scopes -join ', ')"
Connected as: [email protected]
Scopes : UserAuthenticationMethod.Read.All, User.Read.All
Getting All Users with Get-MgUser and Paging
Large tenants require paging through the user list. The Graph SDK handles pagination automatically when you use -All on Get-MgUser. For very large tenants, consider filtering to licensed users only with -Filter to reduce API call volume.
# Get all licensed users (excludes guest accounts without licenses)
$users = Get-MgUser -All `
-Filter "accountEnabled eq true" `
-Property Id, DisplayName, UserPrincipalName, AccountEnabled `
-ErrorAction Stop
Write-Host "Retrieved $($users.Count) enabled users"
Retrieving Authentication Methods with Get-MgUserAuthenticationMethod
For each user, retrieve their registered authentication methods. The AdditionalProperties hashtable on each method object contains the @odata.type property that identifies the method type. Extract this to classify each user’s MFA posture.
function Get-UserMfaMethods {
param([string]$UserId)
try {
$methods = Get-MgUserAuthenticationMethod -UserId $UserId -ErrorAction Stop
$methods | ForEach-Object {
$_.AdditionalProperties['@odata.type'] -replace '#microsoft.graph.', ''
}
}
catch {
Write-Warning "Could not retrieve methods for user $UserId`: $_"
@('retrievalError')
}
}
# Test against a single user first
Get-UserMfaMethods -UserId $users[0].Id
passwordAuthenticationMethod
microsoftAuthenticatorAuthenticationMethod
phoneAuthenticationMethod
Categorizing Methods: FIDO2, Authenticator App, Phone
Map the raw method type strings to human-readable categories. A user with only passwordAuthenticationMethod has no MFA registered at all — they are the accounts your security team cares most about.
$methodDisplayNames = @{
'passwordAuthenticationMethod' = 'Password'
'microsoftAuthenticatorAuthenticationMethod' = 'Authenticator App'
'phoneAuthenticationMethod' = 'Phone (SMS/Call)'
'fido2AuthenticationMethod' = 'FIDO2 Key'
'windowsHelloForBusinessAuthenticationMethod' = 'Windows Hello'
'softwareOathAuthenticationMethod' = 'OATH Token'
'temporaryAccessPassAuthenticationMethod' = 'Temp Access Pass'
'emailAuthenticationMethod' = 'Email OTP'
}
Identifying Users with Only Password Authentication
Process all users in a loop, collect method data, and classify each user. For large tenants, implement retry logic with exponential backoff when Graph returns HTTP 429 (too many requests).
$mfaReport = [System.Collections.Generic.List[PSCustomObject]]::new()
foreach ($user in $users) {
$rawMethods = Get-UserMfaMethods -UserId $user.Id
$displayMethods= $rawMethods | ForEach-Object { $methodDisplayNames[$_] ?? $_ }
$hasMfa = $rawMethods | Where-Object { $_ -ne 'passwordAuthenticationMethod' -and $_ -ne 'retrievalError' }
$mfaReport.Add([PSCustomObject]@{
DisplayName = $user.DisplayName
UPN = $user.UserPrincipalName
MfaEnabled = [bool]$hasMfa
MethodCount = $rawMethods.Count
Methods = $displayMethods -join '; '
MfaRisk = if (-not $hasMfa) { 'No MFA' } else { 'MFA Registered' }
})
# Basic rate-limit courtesy — Graph throttles at scale
Start-Sleep -Milliseconds 50
}
$noMfa = $mfaReport | Where-Object { $_.MfaRisk -eq 'No MFA' }
Write-Host "Users without MFA: $($noMfa.Count) of $($mfaReport.Count)"
Exporting MFA Status Report to CSV
Export the full report and a filtered no-MFA list. The full report is useful for compliance evidence; the no-MFA list goes directly to the team responsible for remediation.
$mfaReport | Export-Csv -Path 'C:\Reports\MFA-Status-All.csv' -NoTypeInformation
$noMfa | Export-Csv -Path 'C:\Reports\MFA-Status-NoMFA.csv' -NoTypeInformation
Write-Host "Reports written:"
Write-Host " All users : C:\Reports\MFA-Status-All.csv"
Write-Host " No MFA : C:\Reports\MFA-Status-NoMFA.csv"
Common Errors
- Authentication method endpoint requires the beta API profile for some method types. Certain method types — notably
temporaryAccessPassAuthenticationMethodand some FIDO2 properties — are only fully exposed through the Graph beta endpoint. Switch the SDK to beta profile withSelect-MgProfile -Name betabefore the retrieval loop if you need full method data. - Rate limiting at scale — implement retry with backoff for large tenants. Graph throttles authentication method queries aggressively when called for thousands of users in rapid succession. Wrap your
Get-MgUserAuthenticationMethodcall in a retry loop that catches HTTP 429 responses, reads theRetry-Afterheader, and sleeps accordingly before retrying.
Related Cmdlets / See Also
Wrapping Up
The Graph SDK’s authentication methods API provides accurate, per-user MFA registration data that the deprecated MSOnline module never delivered. Paginate through all users, classify method types, flag password-only accounts, and export the results — and you have a compliance-ready MFA audit in a single script run.


