Change default Multifactor with Microsoft Graph PowerShell

Many of my customers have been using Entra ID (Azure Active Directory) MFA for ages. The problem for many of the early adopters is that many users enrolled to use SMS as the default method. Even if they have run campaigns and adoption events, some users just wont change. Some manage to configure the Authenticator app but skip to change the method to default, and keep using SMS. Previously, admins were unable to change the default MFA method. But now there is a change!

Now you can, as an admin, change the default Entra ID MFA method both in Admin portal and in Microsoft Graph!

This blog focus on Microsoft Graph way of changing the default MFA method.

Change default MFA method using graph

First you need to install microsoft graph module

install-module microsoft.graph
import-module microsoft.graph

Then you need to connect to graph. For this to work you will need the following permissions:

  • UserAuthenticationMethod.ReadWrite.All
  • User.Read.All

If you run this you will get a prompt to consent the permissions if using it for the first time

Connect-MgGraph -scopes "User.Read.All,UserAuthenticationMethod.ReadWrite.All"

Lets get the user to change the default method for

$MgUser = Get-MgUser -UserId "mr.tbone@tbone.se"

There is no ready command to set the default MFA method, so you need to send a graph request to Patch the setting for the user. The graph URI to Patch is this:

$uri = “https://graph.microsoft.com/beta/users/$($MgUser.id)/authentication/signInPreferences”

On this URI I have found 3 interesting properties: we can set 2 properties:

  • systemPreferredAuthenticationMethod – The System-preferred multifactor authentication selected by the global setting
  • isSystemPreferredAuthenticationMethodEnabled – determine if System-preferred multifactor authentication should be enforced on this user
  • userPreferredMethodForSecondaryAuthentication – The default auth method selected by user or set by admin

Read more on System-preferred multifactor authentication – Authentication methods policy HERE

To get the current values for the user, we can use the same URI and post a GET

Invoke-MgGraphRequest -uri $uri -Method GET

The result could look like this:

The different MFA methods you can push into the users default setting are:

  • push – push notification in Microsoft Authenticator
  • oath – 6 digit code from authentication app.
  • voiceMobile – Microsoft call upp your primary mobile phone
  • voiceAlternateMobile – Microsoft call upp your alternate mobile phone
  • voiceOffice – Microsoft call upp your office phone
  • sms – 6 digit code from SMS text message to the mobile phone

As you can see there are some methods missing, but might appear in later updates.

We take the method we want to set as default and build a body to attach to the Patch:

$body = @’
{“userPreferredMethodForSecondaryAuthentication”: “push”}
‘@

If we do not want System-preferred multifactor authentication to be enforced we can also add that property:

$body = @’
{“isSystemPreferredAuthenticationMethodEnabled”: false
“userPreferredMethodForSecondaryAuthentication”: “push”}
‘@

The result can look like this:


If we then request the settings with GET we can verify the result:

Change Default MFA method on all users

I have written a script to set the users default method to the specified one.
It will fist check if the user is compliant to use that method by getting the registered methods for each user.

Script can be found on my GITHUB

<#PSScriptInfo
.SYNOPSIS
    Script to set Default Password

.DESCRIPTION
    This script will set the default MFA method for a specific user of for all users in the tenant to the preferred method defined in the script.
        
.EXAMPLE
    .\set-MFADefaultMethod.ps1
    Will set the default MFA method for all users in the tenant to the preferred method defined in the script.
    .\set-MFADefaultMethod.ps1 -UserPrincipalName mr.tbone@tbone.se
    Will set the default MFA method for the specified user

.NOTES
    Written by Mr-Tbone (Tbone Granheden) Coligo AB
    torbjorn.granheden@coligo.se

.VERSION
    1.0

.RELEASENOTES
    1.0 2023-09-18 Initial Build
.AUTHOR
    Tbone Granheden 
    @MrTbone_se

.COMPANYNAME 
    Coligo AB

.GUID 
    00000000-0000-0000-0000-000000000000

.COPYRIGHT
    Feel free to use this, But would be grateful if My name is mentioned in Notes 

.CHANGELOG
    1.0.2309.1 - Initial Version   
#>

#region ---------------------------------------------------[Set script requirements]-----------------------------------------------
#Requires -Version 7.0
#endregion

#region ---------------------------------------------------[Script Parameters]-----------------------------------------------
param(
  [Parameter(
    Mandatory = $false,
    ParameterSetName  = "UserPrincipalName",
    HelpMessage = "Enter a single UserPrincipalName",
    Position = 0
    )]
  [string[]]$UserPrincipalName
)
#endregion

#region ---------------------------------------------------[Modifiable Parameters and defaults]------------------------------------
$UserDefaultMethod = "push" # Set the preferred MFA method here
$SystemPreferredMethodEnabled = "true" # Set to true if you want to enable the system preferred authentication method
#endregion

#region ---------------------------------------------------[Set global script settings]--------------------------------------------
Set-StrictMode -Version Latest
#endregion

#region ---------------------------------------------------[Static Variables]------------------------------------------------------
$AllMgUsers       = @()

#Log File Info
#endregion

#region ---------------------------------------------------[Import Modules and Extensions]-----------------------------------------
Import-Module Microsoft.Graph.users
Import-Module Microsoft.Graph.Authentication
#endregion

#region ---------------------------------------------------[Functions]------------------------------------------------------------
#endregion

#region ---------------------------------------------------[[Script Execution]------------------------------------------------------

# Connect to Graph
Connect-MgGraph -scopes "User.Read.All,UserAuthenticationMethod.ReadWrite.All" -NoWelcome

if ($UserPrincipalName) {$AllMgUsers += Get-MgUser -UserId $UserPrincipalName}
else{$AllMgUsers = Get-MgUser -Filter "userType eq 'Member'"  -all}
$TotalMgUsers = $AllMgUsers.Count

# Get all the user's authentication methods. Batches is faster than getting the user and then the methods
$starttime = get-date
$AllAuthMethods = @()
for($i=0;$i -lt $TotalMgUsers;$i+=20){
    $req = @{}                
    # Use select to create hashtables of id, method and url for each call                                     
    if($i + 19 -lt $TotalMgUsers){
        $req['requests'] = ($AllMgUsers[$i..($i+19)] 
            | select @{n='id';e={$_.id}},@{n='method';e={'GET'}},`
            @{n='url';e={"/users/$($_.id)/authentication/methods"}})
    } else {
        $req['requests'] = ($AllMgUsers[$i..($TotalMgUsers-1)] 
            | select @{n='id';e={$_.id}},@{n='method';e={'GET'}},`
            @{n='url';e={"/users/$($_.id)/authentication/methods"}})
    }
    $response = invoke-mggraphrequest -Method POST `
        -URI "https://graph.microsoft.com/beta/`$batch" `
        -body ($req | convertto-json)
    $CurrentMgUser = $i  
    $response.responses | foreach {
        if($_.status -eq 200 -and $_.body.value -ne $null){
            $AuthMethod  = [PSCustomObject]@{
                "userprincipalname" = $AllMgUsers[$CurrentMgUser].userPrincipalName
                "AdditionalProperties" = $_.body
            }
            $AllAuthMethods += $AuthMethod
        } else {
            # request failed
            #write-host "the request for signInPreference failed"
        }
        $CurrentMgUser++
    }
#progressbar
    $Elapsedtime = (get-date) - $starttime
    $timeLeft = [TimeSpan]::FromMilliseconds((($ElapsedTime.TotalMilliseconds / $CurrentMgUser) * ($TotalMgUsers - $CurrentMgUser)))
    Write-Progress -Activity "Getting Authentication Methods for users $($CurrentMgUser) of $($TotalMgUsers)" `
        -Status "Est Time Left: $($timeLeft.Hours) Hours, $($timeLeft.Minutes) Minutes, $($timeLeft.Seconds) Seconds" `
        -PercentComplete $([math]::ceiling($($CurrentMgUser / $TotalMgUsers) * 100))
}

# Build the JSON Body request
$body = @'
{
    "isSystemPreferredAuthenticationMethodEnabled": SystemPreferredMethodEnabled,
    "userPreferredMethodForSecondaryAuthentication": "UserDefaultMethod"
}
'@
$body = $body -replace 'SystemPreferredMethodEnabled', $SystemPreferredMethodEnabled
$body = $body -replace 'UserDefaultMethod', $UserDefaultMethod

#Start the loop to set the default method for each user
$CurrentMgUser = 0
$starttime = get-date
foreach($MgUser in $AllMgUsers){
#Reset all the variables 
    $Authenticator = $false
    $Phone = $false
    $PasswordLess = $false
    $Fido2 = $false
    $TAP = $false
    $WHFB = $false
    $3part = $false
    $Email = $false

    $uri = "https://graph.microsoft.com/beta/users/$($Mguser.id)/authentication/signInPreferences"
#Get the current setting, if it matches the preferred method, do nothing, else set the preferred method
    $CurrentDefaults = Invoke-MgGraphRequest -uri $uri -Method GET -OutputType PSObject
    If ($CurrentDefaults.userPreferredMethodForSecondaryAuthentication -eq $UserDefaultMethod){}
    else{
        #Get the user's authentication methods
        $mfaData = $AllAuthMethods | where userprincipalname -eq $mguser.UserPrincipalName | select -ExpandProperty AdditionalProperties
        # Populate $userobject with MFA methods from $mfaData
        if ($mfaData) {
            ForEach ($method in $mfaData.value) {
                Switch ($method['@odata.type']) {
                    "#microsoft.graph.microsoftAuthenticatorAuthenticationMethod" {
                        # Microsoft Authenticator App
                        $Authenticator = $true
                    }
                    "#microsoft.graph.phoneAuthenticationMethod" {
                        # Phone authentication
                        $Phone = $true
                    }
                    "#microsoft.graph.passwordlessMicrosoftAuthenticatorAuthenticationMethod" {
                        # Passwordless
                        $PasswordLess = $true
                    }
                    "#microsoft.graph.fido2AuthenticationMethod" {
                        # FIDO2 key
                        $Fido2 = $true
                    }
                    "microsoft.graph.temporaryAccessPassAuthenticationMethod" {
                        # Temporary Access pass
                        $TAP = $true
                    }
                    "#microsoft.graph.windowsHelloForBusinessAuthenticationMethod" {
                        # Windows Hello
                        $WHFB = $true
                    }
                    "#microsoft.graph.softwareOathAuthenticationMethod" {
                        # ThirdPartyAuthenticator
                        $3part = $true
                    }
                    "#microsoft.graph.emailAuthenticationMethod" {
                        # Email Authentication
                        $Email = $true
                    }
                }
            }
        }
        #Set the default method if the user has a registered method that matches the preferred method
        If ((($UserDefaultMethod -eq "push") -or ($UserDefaultMethod -eq "oath"))-and ($Authenticator -eq $true))
            {
            Invoke-MgGraphRequest -uri $uri -Body $body -Method PATCH
        }
        elseIf ((($UserDefaultMethod -eq "phone") -or ($UserDefaultMethod -eq "voiceMobile") -or ($UserDefaultMethod -eq "voiceAlternateMobile") -or ($UserDefaultMethod -eq "voiceOffice") -or ($UserDefaultMethod -eq "sms")) -and ($Phone -eq $true))
            {
            Invoke-MgGraphRequest -uri $uri -Body $body -Method PATCH
        }
        else{
            #User has no registered method that matches the preferred method
        }
    }
    $CurrentMgUser++
    
    #progressbar
    if($CurrentMgUser % 100 -eq 0){
        $Elapsedtime = (get-date) - $starttime
        $timeLeft = [TimeSpan]::FromMilliseconds((($ElapsedTime.TotalMilliseconds / $CurrentMgUser) * ($TotalMgUsers - $CurrentMgUser)))
        Write-Progress -Activity "Getting Authentication Methods for users $($CurrentMgUser) of $($TotalMgUsers)" `
            -Status "Est Time Left: $($timeLeft.Hours) Hours, $($timeLeft.Minutes) Minutes, $($timeLeft.Seconds) Seconds" `
            -PercentComplete $([math]::ceiling($($CurrentMgUser / $TotalMgUsers) * 100))
        }
}
Disconnect-Graph
#endregion

About The Author

Mr T-Bone

Torbjörn Tbone Granheden is a Solution Architect for Modern Workplace at Coligo AB. Most Valuable Professional (MVP) on Enterprise Mobility. Certified in most Microsoft technologies and over 23 years as Microsoft Certified Trainer (MCT)

You may also like...