So I’ve blogged about this before too, but times change and monitoring MFA usage is becoming a little more difficult . Microsoft allows per-user MFA, Security Defaults, and Conditional Access all to be used concurrently. I’ve created this monitoring script that returns which users seem to fall out of any Multi-factor authentication scope, and also reports what type of authentication is currently active on the tenant. Using the normal PowerShell methods you can only find if a user has per-user MFA enabled, if a user uses Conditional Access or Security Defaults it shows the per-user MFA state as disabled, which is a little annoying.
So the script checks the following to make sure we know if all users have MFA or not;
- Do users have per-user MFA enabled?
- Is Security Defaults enabled?
- Is there a CA policy that enforces MFA for all users?
This should give you a slightly better report of exactly where there are gaps in your security settings. If users don’t have multi-factor authentication enabled you can help them enable it as soon as possible. I’ve included two scripts, one for all tenants, and one for a specific tenant. Of course as always I’m not saying the script is 100% complete, but feel free edit it to your own needs and policies. 🙂
For the script you’ll need the secure application model, I’ve also posted a new video about the Secure Application Model right here, in case you’ve never set that up.
All Tenants Script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
######### Secrets #########
$ApplicationId = 'ApplicationID'
$ApplicationSecret = 'ApplicationSecret' | ConvertTo-SecureString -Force -AsPlainText
$TenantID = 'TenantID'
$RefreshToken = 'LongRefreshToken'
######### Secrets #########
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$customers = Get-MsolPartnerContract -All
$Baseuri = "https://graph.microsoft.com/beta"
$MFAState = foreach ($customer in $customers) {
$users = Get-MsolUser -TenantId $customer.tenantid -all
$PerUserMFA = foreach ($user in $users) {
$MFAStatus = if ($null -ne $user.StrongAuthenticationUserDetails) { ($user.StrongAuthenticationMethods | Where-Object { $_.IsDefault -eq $true }).methodType } else { "Disabled" }
[PSCustomObject]@{
"DisplayName" = $user.DisplayName
"UPN" = $user.UserPrincipalName
"MFA Type" = $MFAStatus
}
}
try {
$CustGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes "https://graph.microsoft.com/.default" -ServicePrincipal -Tenant $customer.TenantId
$Header = @{ Authorization = "Bearer $($CustGraphToken.AccessToken)" }
$SecureDefaultsState = (Invoke-RestMethod -Uri "$baseuri/policies/identitySecurityDefaultsEnforcementPolicy" -Headers $Header -Method get -ContentType "application/json").IsEnabled
$CAPolicies = (Invoke-RestMethod -Uri "$baseuri/identity/conditionalAccess/policies" -Headers $Header -Method get -ContentType "application/json").value
}
catch {
$CAPolicies = $false
}
$EnforcedForUsers = foreach ($Policy in $CAPolicies) {
if ($policy.grantControls.builtincontrols -ne 'mfa') { continue }
if ($Policy.conditions.applications) {
[PSCustomObject]@{
Name = $policy.displayName
Target = 'Specific Applications'
}
continue
}
if ($Policy.conditions.users.includeUsers -eq "All") {
[PSCustomObject]@{
Name = $policy.displayName
Target = 'All Users'
}
}
}
$enforced = if ($EnforcedForUsers | Where-Object -Property Target -eq "All Users") { $True } else { $false }
[PSCustomObject]@{
TenantName = $customer.DefaultDomainName
UserList = $PerUserMFA
'Secure Defaults Enabled' = $SecureDefaultsState
'Conditional Access' = $CAPolicies
'Conditional Access Enforced MFA' = $Enforced
}
}
if ($MFAState.'Security Defaults Enabled' -eq $false -or $MFAState.'Conditional Access Enforced MFA') {
$MFAState.userlist | Where-Object -Property "MFA Type" -eq "Disabled"
}
|
Single Tenant script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
######### Secrets #########
$ApplicationId = 'ApplicationID'
$ApplicationSecret = 'ApplicationSecret' | ConvertTo-SecureString -Force -AsPlainText
$TenantID = 'TenantID'
$RefreshToken = 'LongRefreshToken'
$clienttenantID = "clientname.onmicrosoft.com"
######### Secrets #########
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$Customer = Get-MsolPartnerContract -All | Where-Object -property DefaultDomainName -eq $clienttenantID
$users = Get-MsolUser -all -TenantId $Customer.TenantId
$PerUserMFA = foreach ($user in $users) {
$MFAStatus = if ($null -ne $user.StrongAuthenticationUserDetails) { ($user.StrongAuthenticationMethods | Where-Object { $_.IsDefault -eq $true }).methodType } else { "Disabled" }
[PSCustomObject]@{
"DisplayName" = $user.DisplayName
"UPN" = $user.UserPrincipalName
"MFA Type" = $MFAStatus
}
}
try{
$CustGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes "https://graph.microsoft.com/.default" -ServicePrincipal -Tenant $clienttenantID
$Header = @{ Authorization = "Bearer $($CustGraphToken.AccessToken)" }
$SecureDefaultsState = (Invoke-RestMethod -Uri "$baseuri/policies/identitySecurityDefaultsEnforcementPolicy" -Headers $Header -Method get -ContentType "application/json").IsEnabled
$CAPolicies = (Invoke-RestMethod -Uri "$baseuri/identity/conditionalAccess/policies" -Headers $Header -Method get -ContentType "application/json").value
} catch {
$CAPolicies = $false
}
$EnforcedForUsers = foreach ($Policy in $CAPolicies) {
if ($policy.grantControls.builtincontrols -ne 'mfa') { continue }
if ($Policy.conditions.applications) {
[PSCustomObject]@{
Name = $policy.displayName
Target = 'Specific Applications'
}
continue
}
if ($Policy.conditions.users.includeUsers -eq "All") {
[PSCustomObject]@{
Name = $policy.displayName
Target = 'All Users'
}
}
}
$enforced = if ($EnforcedForUsers | Where-Object -Property Target -eq "All Users") { $True } else { $false }
$MFAState = [PSCustomObject]@{
TenantName = $customer.DefaultDomainName
UserList = $PerUserMFA
'Secure Defaults Enabled' = $SecureDefaultsState
'Conditional Access' = $CAPolicies
'Conditional Access Enforced MFA' = $Enforced
}
if ($MFAState.'Security Defaults Enabled' -eq $false -or $MFAState.'Conditional Access Enforced MFA' -eq $false) {
$MFAState.userlist | Where-Object -Property "MFA Type" -eq "Disabled"
}
|
Like I said, this should help your reporting a little and gives you the ability to alert better, at least until Microsoft gives us a programmatic MFA testing utility, or something like that.
As always, Happy PowerShelling!