Microsoft is pushing organisations away from legacy per-user MFA towards Conditional Access. If you're still using the old MFA portal, here's how to migrate.
Legacy vs Modern MFA
Legacy (being deprecated):
- Per-user MFA enabled in users.microsoft.com
- Limited policy options
- No device state awareness
- Basic "enabled/enforced/disabled" states
Modern (recommended):
- Conditional Access policies
- Authentication Methods policies
- Device compliance integration
- Risk-based authentication
- Passwordless options
Step 1: Audit Current State
Check Legacy MFA Status
# Connect to MSOnline
Connect-MsolService
# Get users with legacy MFA
Get-MsolUser -All | Where-Object {
$_.StrongAuthenticationRequirements.State -ne $null
} | Select-Object DisplayName, UserPrincipalName,
@{N='MFAState';E={$_.StrongAuthenticationRequirements.State}}
Check Conditional Access
# Connect to Graph
Connect-MgGraph -Scopes "Policy.Read.All"
# List CA policies requiring MFA
Get-MgIdentityConditionalAccessPolicy | Where-Object {
$_.GrantControls.BuiltInControls -contains "mfa"
} | Select-Object DisplayName, State
Step 2: Create Conditional Access Policies
Baseline Policy: Require MFA for All Users
$params = @{
DisplayName = "Require MFA for all users"
State = "enabledForReportingButNotEnforced" # Start in report-only
Conditions = @{
Users = @{
IncludeUsers = @("All")
ExcludeUsers = @("BREAK_GLASS_ACCOUNT_ID")
}
Applications = @{
IncludeApplications = @("All")
}
ClientAppTypes = @("all")
}
GrantControls = @{
Operator = "OR"
BuiltInControls = @("mfa")
}
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $params
Admin Policy: Always Require MFA
$adminRoles = @(
"62e90394-69f5-4237-9190-012177145e10", # Global Admin
"194ae4cb-b126-40b2-bd5b-6091b380977d", # Security Admin
"f28a1f50-f6e7-4571-818b-6a12f2af6b6c", # SharePoint Admin
"29232cdf-9323-42fd-ade2-1d097af3e4de" # Exchange Admin
)
$params = @{
DisplayName = "Require MFA for admins - Always"
State = "enabled"
Conditions = @{
Users = @{
IncludeRoles = $adminRoles
}
Applications = @{
IncludeApplications = @("All")
}
}
GrantControls = @{
Operator = "OR"
BuiltInControls = @("mfa")
}
SessionControls = @{
SignInFrequency = @{
Value = 4
Type = "hours"
IsEnabled = $true
}
}
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $params
Step 3: Configure Authentication Methods
Enable Modern Methods
# Enable Microsoft Authenticator
$authenticatorConfig = @{
"@odata.type" = "#microsoft.graph.microsoftAuthenticatorAuthenticationMethodConfiguration"
State = "enabled"
IncludeTargets = @(
@{
TargetType = "group"
Id = "all_users"
IsRegistrationRequired = $false
AuthenticationMode = "any"
}
)
}
Update-MgPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration `
-AuthenticationMethodConfigurationId "MicrosoftAuthenticator" `
-BodyParameter $authenticatorConfig
Enable FIDO2 Keys
$fido2Config = @{
"@odata.type" = "#microsoft.graph.fido2AuthenticationMethodConfiguration"
State = "enabled"
IsAttestationEnforced = $false
IsSelfServiceRegistrationAllowed = $true
KeyRestrictions = @{
IsEnforced = $false
EnforcementType = "allow"
AaGuids = @()
}
}
Update-MgPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration `
-AuthenticationMethodConfigurationId "Fido2" `
-BodyParameter $fido2Config
Step 4: Temporary Access Pass for Migration
Enable TAP for users who need to re-register:
$tapConfig = @{
"@odata.type" = "#microsoft.graph.temporaryAccessPassAuthenticationMethodConfiguration"
State = "enabled"
DefaultLifetimeInMinutes = 60
DefaultLength = 8
IsUsableOnce = $true
IncludeTargets = @(
@{
TargetType = "group"
Id = "HELPDESK_GROUP_ID"
}
)
}
Update-MgPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration `
-AuthenticationMethodConfigurationId "TemporaryAccessPass" `
-BodyParameter $tapConfig
Step 5: Migration Process
For Each User
- Create TAP (if needed for re-registration):
$tap = New-MgUserAuthenticationTemporaryAccessPassMethod `
-UserId "[email protected]" `
-LifetimeInMinutes 60 `
-IsUsableOnce $true
$tap.TemporaryAccessPass # Give this to user
-
User registers new method at https://aka.ms/mfasetup
-
Disable legacy MFA:
$mfa = @{
StrongAuthenticationRequirements = @()
}
Set-MsolUser -UserPrincipalName "[email protected]" -StrongAuthenticationRequirements $mfa
Step 6: Block Legacy Authentication
After migration, block protocols that don't support MFA:
$params = @{
DisplayName = "Block legacy authentication"
State = "enabled"
Conditions = @{
Users = @{
IncludeUsers = @("All")
ExcludeUsers = @("BREAK_GLASS_ACCOUNT_ID")
}
Applications = @{
IncludeApplications = @("All")
}
ClientAppTypes = @("exchangeActiveSync", "other")
}
GrantControls = @{
Operator = "OR"
BuiltInControls = @("block")
}
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $params
Monitoring the Migration
// Sign-in logs showing legacy auth attempts
SigninLogs
| where TimeGenerated > ago(7d)
| where ClientAppUsed in ("Exchange ActiveSync", "IMAP4", "MAPI Over HTTP", "Offline Address Book", "Other clients", "POP3", "SMTP")
| summarize count() by UserPrincipalName, ClientAppUsed
| order by count_ desc
Need help modernising your authentication infrastructure? Get in touch - we help organisations implement secure identity solutions.