Security & identity

Azure mandatory MFA phase 2: fix CLI, PowerShell and Terraform sign-ins

Phase 2 of Azure mandatory MFA blocks create, update and delete calls from user accounts without MFA. Find the scripts that break and move them to managed identities, service principals or OIDC.

12 min read
On this page

Phase 2 of Azure mandatory multifactor authentication requires every user account that creates, updates or deletes resources through Azure Resource Manager to sign in with MFA, whether the call comes from Azure CLI, Azure PowerShell, Terraform, an SDK or the REST API. Interactive users fix it by signing in with MFA, ideally enforced at sign-in by Conditional Access. Unattended scripts and pipelines that use a user account must move to a workload identity: a managed identity, a service principal with a certificate, or workload identity federation (OIDC), none of which are subject to the enforcement.

Who this is for and what you will have at the end

This guide is for Azure administrators and platform engineers whose scripts, scheduled tasks or CI/CD pipelines started failing with RequestDisallowedByPolicy or "Interactive authentication is needed", or who want to find those scripts before they fail.

At the end you will have:

  • A clear picture of what phase 2 covers and what it doesn't.
  • A list of user accounts that run Azure CLI or Azure PowerShell without MFA, from the sign-in logs.
  • Replacement sign-in code for Azure CLI, Azure PowerShell, Terraform and GitHub Actions using workload identities.
  • An optional Azure Policy assignment that audits or blocks non-MFA changes on your own schedule.

What phase 2 enforces

Phase 1 covered the Azure portal, Microsoft Entra admin center and Intune admin center. Phase 2 started gradually on 1 October 2025 and covers the clients most automation uses:

ClientApp IDPhase 2 scope
Azure CLI04b07795-8ddb-461a-bbee-02f9e1bf7b46Create, update, delete
Azure PowerShell1950a258-227b-4e31-a9cf-717495945fc2Create, update, delete
Azure mobile app0c1307d4-29d6-4389-a11c-5cbe7f65d7faCreate, update, delete
Infrastructure as code toolsUse the Azure CLI or Azure PowerShell IDsCreate, update, delete
REST API (control plane) and Azure SDKNot applicableCreate, update, delete

Points that decide whether a script breaks:

  • Enforcement is server-side. Microsoft enforces it on Azure Resource Manager, so any request that targets https://management.azure.com is in scope, whatever client sends it.
  • Reads are not affected. A script that only lists or reads resources keeps working.
  • Workload identities are exempt. Managed identities and service principals aren't affected by either phase.
  • User accounts used as service accounts are not exempt. Accounts synchronized from Active Directory, break-glass accounts and accounts excluded from your Conditional Access policies all need MFA. Exclusions in your own policies don't apply to this system enforcement.
  • Microsoft Graph is generally out of scope. Only requests to Azure Resource Manager are enforced.
  • Public cloud only. Azure for US Government and other sovereign clouds aren't currently enforced.
  • No opt-out. Self-service postponement allowed a start date up to 1 July 2026. After enforcement begins, a Global Administrator can ask Microsoft Help and Support to lift it temporarily.

To confirm the state of your tenant, sign in to the Azure portal as a Global Administrator, browse to https://aka.ms/postponePhase2MFA and check the banner on the Multifactor authentication (Phase 2) page.

Prerequisites

  • Reports Reader or Security Reader to read sign-in logs, and a Log Analytics workspace receiving Microsoft Entra sign-in logs if you want to use KQL.
  • Rights to create workload identities and assign Azure roles on the target scope, for example Owner, or Contributor plus User Access Administrator. Creating federated credentials on an app registration needs the app owner or Application Administrator, Cloud Application Administrator, Global Administrator or Hybrid Identity Administrator.
  • Azure CLI 2.76 or later and Azure PowerShell 14.3 or later (Az.Accounts 5.2.0 or later). Older versions return less useful errors.

Step 1: Find the user accounts that run automation

Microsoft recommends querying sign-ins by the application IDs in the table above. If your sign-in logs flow to Log Analytics, this query lists user accounts that sign in to Azure CLI or Azure PowerShell, how often they do so with single-factor authentication, and whether they use the password-based ROPC flow, which can never satisfy MFA:

SigninLogs
| where TimeGenerated > ago(30d)
| where AppId in ("04b07795-8ddb-461a-bbee-02f9e1bf7b46", "1950a258-227b-4e31-a9cf-717495945fc2")
| summarize SignIns = count(),
            SingleFactor = countif(AuthenticationRequirement == "singleFactorAuthentication"),
            Protocols = make_set(AuthenticationProtocol),
            IPs = make_set(IPAddress, 10),
            LastSeen = max(TimeGenerated)
          by UserPrincipalName, AppDisplayName
| order by SingleFactor desc

Accounts with a high SingleFactor count, ropc in Protocols, or a fixed set of server IP addresses are almost always scripts. Without Log Analytics, use the portal: Entra ID > Monitoring & health > Sign-in logs, filter Application on Azure CLI or Azure PowerShell, and check both the interactive and non-interactive user sign-in tabs.

For each account, record where the script runs (an Azure VM, an on-premises server, a pipeline agent, a developer laptop), which subscriptions it changes and which roles it holds. That decides the replacement identity.

Step 2: Choose the replacement identity

Where the automation runsUseWhy
Azure VM, App Service, Functions, Container Apps, AKSManaged identityNo credential to store or rotate
GitHub Actions, Azure DevOps, Kubernetes, another cloudWorkload identity federation (OIDC) on an app registration or user-assigned managed identityExchanges the platform's token; no secret
On-premises server or anything elseService principal with a certificateNon-interactive; avoid client secrets where you can
A person at a terminalTheir own account with MFAInteractive work is not automation

Microsoft's PowerShell guidance describes managed identities as less flexible but more secure than service principals, and federated identities as the best fit for GitHub Actions, Kubernetes and workloads outside Azure.

Step 3: Update Azure CLI scripts

Grant the new identity the same role, at the narrowest scope that works:

APP_ID="11111111-1111-1111-1111-111111111111"
SCOPE="/subscriptions/<subscription-id>/resourceGroups/<resource-group>"
 
az role assignment create --assignee "$APP_ID" --role Contributor --scope "$SCOPE"

Then replace the az login -u user@contoso.com -p ... line.

On an Azure resource with a managed identity:

# System-assigned managed identity
az login --identity
 
# User-assigned managed identity: use --client-id, --object-id or --resource-id
az login --identity --client-id <client_id>

Anywhere else, with a service principal and a certificate. The PEM file must contain the certificate appended to the private key:

az login --service-principal --username APP_ID --certificate /path/to/cert.pem --tenant TENANT_ID

If you must use a client secret, Azure CLI supports --password; when the secret starts with a hyphen, attach it with --password=CLIENT_SECRET so the parser doesn't read it as an option.

Step 4: Update Azure PowerShell scripts

Connect-AzAccount -Credential $cred with a user name and password uses ROPC, which is incompatible with MFA. Replace it with one of these:

# System-assigned managed identity
Connect-AzAccount -Identity
 
# User-assigned managed identity
Connect-AzAccount -Identity -AccountId <user-assigned-identity-clientId-or-resourceId>
 
# Service principal with a certificate from the local certificate store
Connect-AzAccount -ServicePrincipal -ApplicationId $appId -Tenant $tenantId -CertificateThumbprint <thumbprint>

For a service principal with a secret, build a credential from the application ID and secret and pass -ServicePrincipal -Credential $pscredential -Tenant $tenantId. Azure PowerShell stores that secret in AzureRmContext.json under the user profile, so protect the folder or prefer a certificate.

If one account has access to several tenants, always pass -Tenant (or -TenantId) so the sign-in targets the tenant you mean rather than the first one found.

Step 5: Update Terraform

The azurerm provider doesn't care which identity you use as long as Azure accepts the token. HashiCorp recommends Azure CLI authentication when you run Terraform locally and a service principal or managed identity when you run it non-interactively.

Locally: run az login as yourself with MFA. If your sign-in didn't include MFA, a plan, which mostly reads, can succeed while the apply fails, because only reads are exempt.

On an Azure VM or self-hosted agent with a managed identity:

export ARM_USE_MSI=true
export ARM_SUBSCRIPTION_ID=00000000-0000-0000-0000-000000000000
export ARM_TENANT_ID=00000000-0000-0000-0000-000000000000
# Only for a user-assigned identity:
export ARM_CLIENT_ID=00000000-0000-0000-0000-000000000000

In a pipeline with OIDC (provider 3.7.0 or later):

provider "azurerm" {
  features {}
  use_oidc = true
}

Supply ARM_CLIENT_ID, ARM_TENANT_ID and ARM_SUBSCRIPTION_ID as pipeline variables rather than in the file. In GitHub Actions the provider picks up the runner's token request variables automatically when the job has id-token: write. In Azure DevOps, set ARM_USE_OIDC and ARM_ADO_PIPELINE_SERVICE_CONNECTION_ID, and reference the service connection in at least one task. Since provider 3.44, Azure CLI authentication also works when the CLI itself is signed in as a service principal or managed identity. If you use the azurerm backend for state, configure its authentication too.

Step 6: Move GitHub Actions to OIDC

Create a federated credential that trusts your repository. The subject must match the workflow exactly; for a job that uses the production environment:

{
  "name": "github-production",
  "issuer": "https://token.actions.githubusercontent.com",
  "subject": "repo:contoso/infrastructure:environment:production",
  "description": "Production deployments",
  "audiences": ["api://AzureADTokenExchange"]
}
az ad app federated-credential create --id <app-id-or-object-id> --parameters credential.json

Other subject formats are repo:<org>/<repo>:ref:refs/heads/<branch>, repo:<org>/<repo>:ref:refs/tags/<tag> and repo:<org>/<repo>:pull-request. Wildcards aren't supported, and an app or user-assigned managed identity can hold at most 20 federated credentials. Then sign in with azure/login and no secret:

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

Add enable-AzPSSession: true if later steps use Azure PowerShell.

Step 7: Make interactive users satisfy MFA at sign-in

For people, the cleanest fix is a Conditional Access policy so that the token already carries MFA. Microsoft's guidance: target the users, select Microsoft Admin Portals and Windows Azure Service Management API under Target resources, grant with Require authentication strength > Multifactor authentication, and start in Report-only. Tenants without Microsoft Entra ID P1 can use security defaults. Note that Azure CLI sign-in requests only User.Read, which matters for policies that target All resources with exclusions; see the baseline scopes enforcement change.

Optional: self-enforce with Azure Policy first

Two built-in definitions let you audit or block non-MFA changes before or alongside the platform enforcement:

DefinitionIDEffects
Users must authenticate with multi-factor authentication to create or update resources4e6c27d5-a6ee-49cf-b2b4-d8fe90fa2b8bAudit, then Deny
Users must authenticate with multi-factor authentication to delete resourcesdb4a9d17-db75-4f46-9fcb-9f9526604417AuditAction, then DenyAction

Assign them under Policy > Assignments > Assign policy, use resource selectors on resourceLocation to start with low-risk regions, add a non-compliance message, and switch to deny later with an effect override. Audit events appear only in the Activity log; the compliance view always shows zero resources because the policy evaluates requests, not resources.

AzureActivity
| where CategoryValue == "Policy"
| extend p = parse_json(Properties)
| extend policies = parse_json(tostring(p.policies))
| mv-expand policies
| where tostring(policies.policyDefinitionId) == "/providers/Microsoft.Authorization/policyDefinitions/4e6c27d5-a6ee-49cf-b2b4-d8fe90fa2b8b"
| project TimeGenerated, ResourceId, OperationNameValue

Verify

  • Rerun the Step 1 query a week after each migration: the old user account should disappear from Azure CLI and Azure PowerShell sign-ins.
  • In the sign-in logs, check the service principal and managed identity sign-in tabs for the new identity.
  • Run one create, update and delete operation from each migrated script in a test resource group.
  • Disable the old user account's role assignments, then the account itself once nothing fails.

Troubleshooting

(RequestDisallowedByPolicy) Resource was disallowed by policy ... Users must authenticate with multi-factor authentication to create or update resources. The token has no MFA claim. Azure CLI 2.76 and later prints the fix: az logout, then az login --tenant <tenant-id> --scope "https://management.core.windows.net//.default" --claims-challenge "<claims-challenge-token>". In unattended code, switch to a workload identity.

Resource was disallowed by policy. Users must use MFA for Create operation. in Azure PowerShell 14.3 and later. Run the suggested Connect-AzAccount -Tenant (Get-AzContext).Tenant.Id -ClaimsChallenge "<claims-challenge-token>" interactively, or require MFA at sign-in.

SharedTokenCacheCredential authentication unavailable on Az 14.2.0 or earlier. Upgrade to Az 14.3.0 and Az.Accounts 5.2.0 to see the real policy error.

UsernamePasswordCredential authentication failed: ... 400 (BadRequest) from Connect-AzAccount -Credential. This is ROPC, which doesn't support MFA. Replace it as shown in Step 4.

WARNING: Unable to acquire token for tenant ... User interaction is required. Azure PowerShell tried the first tenant it found and that tenant requires MFA. Pass -TenantId explicitly.

OIDC sign-in fails with no clear error. The federated credential subject doesn't match the workflow's branch, tag or environment. Check it character by character; a credential with a wrong subject is created without any error.

az ad sp create-for-rbac --name changed an existing app. Display names aren't unique, so the command can reuse a matching identity and replace its credentials. Use a unique name and target existing identities by app ID or object ID.

Checklist

  • Phase 2 status confirmed on the Multifactor authentication (Phase 2) page.
  • Azure CLI 2.76+ and Azure PowerShell 14.3+ deployed to admin workstations and build agents.
  • User accounts signing in to Azure CLI and Azure PowerShell without MFA listed and owned.
  • Each script moved to a managed identity, a certificate-based service principal or OIDC, with least-privilege role assignments.
  • Terraform pipelines on use_oidc or use_msi; local runs on MFA-backed az login.
  • Conditional Access requiring MFA for Windows Azure Service Management API, tested in report-only first.
  • Break-glass accounts on passkeys or certificate-based authentication.
  • Old service-account users stripped of roles and disabled.

For the wider landing-zone context these identities live in, see the enterprise Azure cloud migration playbook.

References

Questions people ask

Does Azure mandatory MFA apply to service principals and managed identities?

No. Microsoft states that workload identities, such as managed identities and service principals, aren't impacted by either phase of the enforcement. Only user identities are, including user accounts that are used as service accounts to run scripts.

Do read-only Azure CLI and PowerShell commands require MFA?

No. Phase 2 requires MFA for create, update and delete operations sent to Azure Resource Manager. Read operations don't require MFA, which is why a script can list resources successfully and then fail on its first change.

Can I still postpone Azure MFA phase 2 enforcement?

The self-service postponement only allowed a start date up to 1 July 2026. After enforcement begins, a Global Administrator can submit a request to Microsoft Help and Support to temporarily lift it. There is no opt-out.

Which Azure CLI and Azure PowerShell versions should I use?

Microsoft recommends Azure CLI 2.76 and Azure PowerShell 14.3 or later (Az.Accounts 5.2.0 or later). Older versions return vague errors, while newer ones print the exact sign-in command with a claims challenge.

Azure CLIAzure PowerShellTerraformManaged identitiesMicrosoft Entra ID
  1. A Microsoft 365 tenant security baseline you can apply in a day

    Harden a new or existing Microsoft 365 tenant in one working day: emergency access, admin roles, MFA and legacy auth, app consent, email protection, external forwarding, audit logging and Secure Score.

  2. AADSTS50076, 50079 and 50158: fix Microsoft Entra MFA sign-in errors

    What AADSTS50076, AADSTS50079 and AADSTS50158 mean, how to find the policy that demanded MFA in the Entra sign-in logs, and how to fix each one for users, scripts and federated domains.

  3. AADSTS53003 blocked by Conditional Access: find the policy and fix it

    Troubleshoot AADSTS53003 in Microsoft Entra ID: trace the correlation ID to the sign-in log, identify the blocking Conditional Access policy, and fix the user, device or policy without weakening security.