On this page

If you manage Azure infrastructure through CLI, PowerShell, Terraform, or any tool that hits the ARM control plane, every write operation from a user account will require MFA.

Starting October 1, 2025, Microsoft began gradually rolling out Phase 2 of mandatory MFA enforcement. Phase 1 (October 2024) locked down the portal. Phase 2 extends that to Azure CLI, Azure PowerShell, Terraform, Bicep, the REST API, Azure SDKs, and the mobile app. Every create, update, or delete operation against management.azure.com will require MFA. July 1, 2026 is the hard cutoff. No more self-service extensions after that date.

Service principals and managed identities are exempt. Everything else is in scope: every user account, break-glass accounts, B2B guests, AD-synced service accounts, that svc-terraform@ someone provisioned in 2019.


The Timeline

DateWhat Happened
October 2024Phase 1 enforcement begins โ€” Azure portal, Entra admin center, Intune admin center โ€” all CRUD operations require MFA
February 2025Phase 1 extends to Microsoft 365 admin center
October 1, 2025Phase 2 enforcement begins gradual rollout โ€” CLI, PowerShell, Terraform, Bicep, REST API, SDKs, mobile app
July 1, 2026Final postponement deadline โ€” no self-service extensions after this date

If you postponed Phase 1, your Phase 2 postponement was automatically linked to the same date. Global Administrators can adjust the Phase 2 start date, but only until July 1.

After enforcement begins, the only remaining option is a support ticket to Microsoft for a temporary lift โ€” there is no opt-out. Microsoft’s own language: “There’s no way to opt out. This security motion is critical to the safety and security of the Azure platform.”

Note: Sovereign clouds (Azure Government, Azure China) are not currently subject to mandatory MFA enforcement.


What’s In Scope

Phase 2 enforces MFA on create, update, and delete operations against the ARM control plane. Read operations are exempt.

ComponentIn ScopeNotes
Azure CLI (az)YesApp ID: 04b07795-8ddb-461a-bbee-02f9e1bf7b46
Azure PowerShell (Az)YesApp ID: 1950a258-227b-4e31-a9cf-717495945fc2
Terraform / BicepYesUses CLI or PowerShell app IDs for auth
REST API (management.azure.com)YesServer-side enforcement on the ARM endpoint
Azure SDKs (.NET, Python, Go, Java, JS)YesServer-side enforcement on ARM calls
Azure mobile appYesApp ID: 0c1307d4-29d6-4389-a11c-5cbe7f65d7fa
Read operations (any tool)Noaz group list, Get-AzResourceGroup, terraform plan (all exempt)
Data plane (blob storage, Key Vault data, SQL)NoOnly management.azure.com is enforced
Microsoft Graph APIGenerally noNot part of ARM enforcement; Graph has its own CA policies

In practice: az group list won’t trigger an MFA challenge, but az group create will. terraform plan reads state and skips MFA; terraform apply writes resources and requires it.

Decision flow diagram showing Azure MFA Phase 2 enforcement path: identity type check, operation type check, MFA satisfaction check, with examples of exempt and affected operations
Phase 2 enforcement flow: workload identities and read operations bypass MFA. User accounts performing write operations must satisfy Entra-native MFA.

What’s Exempt

Workload identities are fully exempt. Managed identities and service principals are not affected by either phase.

What is not exempt:

  • All user accounts (no exceptions, regardless of role or exclusions)
  • Break-glass / emergency access accounts (must use FIDO2 or certificate-based MFA)
  • B2B guest users (must satisfy MFA in either the home or resource tenant)
  • AD-synced service accounts (if it’s a user object in Entra, it’s in scope)
  • Conditional Access exclusions (CA policy exclusions do not override mandatory enforcement)

Minimum Tool Versions

For the best compatibility with MFA prompts (interactive token refresh, WAM broker support):

  • Azure CLI: 2.76 or later
  • Azure PowerShell: 14.3 or later

I ran this lab on CLI 2.81 and Az.Accounts 5.3.2. Both handled MFA flows cleanly.


Audit Your Exposure

I ran these audit queries in my lab tenant to find user accounts hitting the ARM control plane and check whether they had MFA registered.

Find User Accounts Using CLI and PowerShell

Query Entra sign-in logs for the Azure CLI and Azure PowerShell app IDs. This requires at least the Reports Reader role.

# Get recent sign-in events for Azure CLI and Azure PowerShell (top 50)
$cliAppId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
$psAppId  = "1950a258-227b-4e31-a9cf-717495945fc2"

$uri = "https://graph.microsoft.com/v1.0/auditLogs/signIns?" +
       "`$filter=appId eq '$cliAppId' or appId eq '$psAppId'" +
       "&`$top=50" +
       "&`$select=userDisplayName,userPrincipalName,appDisplayName,appId,createdDateTime,status"

$response = Invoke-MgGraphRequest -Method GET -Uri $uri
$response.value | Select-Object userPrincipalName, appDisplayName, createdDateTime |
    Sort-Object createdDateTime -Descending |
    Format-Table -AutoSize

In my lab tenant, this returned sign-in events immediately:

userPrincipalName                 appDisplayName       createdDateTime
-----------------                 --------------       ---------------
jdahlager@โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ    Microsoft Azure CLI  2026-03-02T21:02:17Z
jdahlager@โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ    Microsoft Azure CLI  2026-03-01T00:22:16Z
jdahlager@โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ    Microsoft Azure CLI  2026-02-12T01:58:36Z

Each of these is a user account that hits ARM write operations after enforcement:

Graph Explorer showing sign-in log query results filtered by Azure CLI and Azure PowerShell app IDs, with appDisplayName fields highlighted in red
Sign-in log query in Graph Explorer. Both entries show appDisplayName: "Microsoft Azure CLI", confirming user accounts are hitting the ARM control plane.

Find Users Without MFA Registered

# Users with no MFA methods registered
$uri = "https://graph.microsoft.com/v1.0/reports/authenticationMethods/" +
       "userRegistrationDetails?" +
       "`$filter=isMfaRegistered eq false" +
       "&`$select=userDisplayName,userPrincipalName,isMfaRegistered,userType"

$headers = @{ ConsistencyLevel = "eventual" }
$response = Invoke-MgGraphRequest -Method GET -Uri $uri -Headers $headers
$response.value | Format-Table userDisplayName, userPrincipalName, userType -AutoSize

My lab tenant returned three accounts without MFA:

userDisplayName    userPrincipalName                       userType
---------------    -----------------                       --------
Test1              Test1@โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ    member
Test2              Test2@โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ    member
Test3              Test3@โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ    member

Any account on this list that also shows up in the CLI/PowerShell sign-in logs will break after enforcement. Here is the same query in Graph Explorer:

Graph Explorer showing MFA registration report with three accounts that have no MFA methods registered, highlighted in red
Three accounts with no MFA registered. Any of these that also appear in the CLI/PowerShell sign-in logs will break after enforcement.

Check for ROPC (Password Grant) Usage

The OAuth 2.0 Resource Owner Password Credentials (ROPC) flow is incompatible with MFA. If any automation uses UsernamePasswordCredential from an Azure SDK, it will break.

# Find ROPC sign-ins (grantType = password)
$uri = "https://graph.microsoft.com/beta/auditLogs/signIns?" +
       "`$filter=authenticationProtocol eq 'ropc'" +
       "&`$top=50" +
       "&`$select=userPrincipalName,appDisplayName,createdDateTime,authenticationProtocol"

$response = Invoke-MgGraphRequest -Method GET -Uri $uri
if ($response.value.Count -eq 0) {
    Write-Output "No ROPC sign-ins found. Good."
} else {
    $response.value | Format-Table -AutoSize
}

Note: authenticationProtocol is currently available for this filter on the beta endpoint. The v1.0 signIns schema doesn’t expose it reliably.

Microsoft has already deprecated UsernamePasswordCredential across all Azure Identity SDK packages:

SDKPackageDeprecated In
JavaScript/Node@azure/identity4.8.0
Pythonazure-identity1.21.0
.NETAzure.Identity1.14.0-beta.2
Goazidentity1.9.0
Javaazure-identity1.16.0-beta.1

If you find ROPC usage, there’s no fix that keeps the password flow. The migration target is a workload identity.


Migration Paths

Same fix for every scenario: replace user accounts in automation with workload identities.

1. Managed Identity (Azure-Hosted Workloads)

If your automation runs on Azure compute (VMs, App Service, Functions, Container Apps, AKS), managed identity is the simplest option โ€” no credentials to manage, no MFA challenge, and system-assigned identities are tied to the resource lifecycle.

# Assign a role to a managed identity
az role assignment create \
  --assignee-object-id <managed-identity-object-id> \
  --assignee-principal-type ServicePrincipal \
  --role "Contributor" \
  --scope "/subscriptions/<subscription-id>/resourceGroups/<rg-name>"

In code, authentication is one line:

from azure.identity import ManagedIdentityCredential
from azure.mgmt.resource import ResourceManagementClient

credential = ManagedIdentityCredential()
client = ResourceManagementClient(credential, subscription_id)

2. Service Principal with Certificate

For workloads outside Azure, or when you need a non-human identity with explicit credential control:

# Create a service principal with a certificate stored in Key Vault
az ad sp create-for-rbac \
  --name "svc-terraform-prod" \
  --role "Contributor" \
  --scopes "/subscriptions/<subscription-id>" \
  --cert "TerraformProdCert" \
  --create-cert \
  --keyvault "your-keyvault-name" \
  --years 1

The --keyvault flag stores the certificate directly in Key Vault instead of dropping a PEM file on disk. If you omit it, the cert lands in the current directory โ€” fine for testing, but not for production.

Authenticate in scripts:

Connect-AzAccount `
  -ServicePrincipal `
  -TenantId "your-tenant-id" `
  -ApplicationId "your-app-id" `
  -CertificateThumbprint "your-cert-thumbprint"

The certificate belongs in Key Vault or your CI system’s secret store.

3. Federated Credentials / OIDC (CI/CD Pipelines)

For GitHub Actions, Azure DevOps, or any OIDC-capable CI system, federated credentials eliminate stored secrets entirely. The CI platform issues a short-lived token that Azure trusts.

GitHub Actions setup:

# Create an app registration
appId=$(az ad app create --display-name "github-deploy-prod" --query appId -o tsv)

# Create the service principal
az ad sp create --id $appId

# Add federated credential for the main branch
az ad app federated-credential create --id $appId --parameters '{
  "name": "github-main",
  "issuer": "https://token.actions.githubusercontent.com",
  "subject": "repo:your-org/your-repo:ref:refs/heads/main",
  "audiences": ["api://AzureADTokenExchange"]
}'

# Assign the role
az role assignment create \
  --assignee $appId \
  --role "Contributor" \
  --scope "/subscriptions/<subscription-id>"

In the GitHub Actions workflow:

permissions:
  id-token: write

steps:
- uses: azure/login@v2
  with:
    client-id: ${{ secrets.AZURE_CLIENT_ID }}
    tenant-id: ${{ secrets.AZURE_TENANT_ID }}
    subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

No client secret, no certificate, no MFA challenge โ€” the OIDC token exchange handles everything.

Decision Table

ScenarioRecommended MethodWhy
Azure-hosted automation (VMs, Functions, AKS)Managed identityZero credential management, tied to resource lifecycle
GitHub Actions / Azure DevOps CI/CDFederated credentials (OIDC)No stored secrets, short-lived tokens
On-premises scriptsService principal + certificateExplicit credential, no MFA, rotatable
Multi-cloud / third-party CIService principal + certificate or OIDCDepends on platform OIDC support
Developer local az loginInteractive MFARegister MFA and move on. This is the intended flow.

Break-Glass Accounts

Break-glass accounts are user accounts, and mandatory MFA enforcement applies to them too โ€” Conditional Access exclusions do not override the system-level requirement.

Microsoft’s guidance is to update break-glass accounts to use passkey (FIDO2) or certificate-based authentication for MFA, both of which satisfy the mandatory requirement without depending on a phone or authenticator app.

If your emergency access procedure relies on a username and password with no MFA, it stops working after enforcement.


Gotchas

  1. AD-synced service accounts are user accounts. If you synced [email protected] from on-premises AD to Entra, it’s a user object and it’s in scope โ€” migrate it to a service principal or managed identity. (Exception: the Entra Connect / Cloud Sync synchronization service account itself is not affected.)

  2. B2B guests must satisfy MFA. The MFA requirement can be met in either the home tenant or the resource tenant, but it must be met. If your partners access your subscription via guest accounts, they need MFA configured somewhere.

  3. Third-party MFA via legacy custom controls doesn’t count. If you rely on a third-party MFA provider integrated through legacy custom controls in Conditional Access, it will not satisfy mandatory MFA enforcement. You must use Entra-native MFA methods or migrate to the External Authentication Methods preview.

  4. Test and dev tenants must comply. There is no “non-production exemption.” Every Entra tenant in the public cloud is subject to enforcement.

  5. Postponement requires elevated access. Global Administrators must elevate access before they can postpone enforcement, and postponement must be done per-tenant.

  6. Post-July 1 options are limited. After the self-service deadline, the only path is a Microsoft support ticket for a temporary lift.

  7. terraform plan is safe, terraform apply is not. Plan only reads state and skips MFA, while apply writes resources and requires it. This catches teams who test in CI with plan and don’t discover the issue until apply fails.

  8. The ROPC flow is dead. Any SDK code using UsernamePasswordCredential will throw exceptions after enforcement, and there’s no workaround that preserves password-based auth.


Resources

Jerrad Dahlager

Jerrad Dahlager, CISSP, CCSP

Cloud Security Architect ยท Adjunct Instructor

Marine Corps veteran and firm believer that the best security survives contact with reality.

Have thoughts on this post? I'd love to hear from you.