Security Azure

Rolling Out Phishing-Resistant Passwordless Auth: FIDO2, Passkeys, and Break-Glass Design

Most MFA does not stop phishing. A push prompt, an OTP, even a phone-based code can be relayed by an adversary-in-the-middle (AiTM) proxy in real time: the user types the code into a fake page, the proxy forwards it, and the attacker walks away with a session cookie. The only thing that breaks that chain is a credential that is cryptographically bound to the origin and never leaves the authenticator. That is what FIDO2 and passkeys give you. This guide is the deployment runbook for getting there on Microsoft Entra ID without locking yourself out.

Why phishing-resistant MFA matters

The attacker economics are simple. AiTM phishing kits (Evilginx, EvilProxy, and their commercial descendants) are cheap, scriptable, and defeat any factor the user can transcribe or approve. Once the proxy captures the post-MFA session token, MFA is irrelevant. Number matching slowed bulk MFA-fatigue attacks but does nothing against a live relay.

FIDO2 (WebAuthn + CTAP2) closes this because the authenticator signs a challenge that includes the relying party ID (the origin). A credential registered for login.microsoftonline.com simply will not produce a valid assertion for login.microsoftonline.evil.com. There is no secret to phish, no code to relay. The private key never leaves the security key or the device’s hardware-backed keystore.

The goal is not “passwordless because passwords are annoying.” The goal is removing every credential a human can be tricked into surrendering. Convenience is the adoption lever; phishing resistance is the security property.

1. Choosing factors

There are three phishing-resistant methods Entra recognizes. Pick deliberately; they have different operational profiles.

Method Bound to Best for Watch-outs
FIDO2 security key (hardware) The key (portable) Admins, shared workstations, BYOD where you can’t manage the device Procurement, loss/replacement logistics
Platform passkey (Windows Hello for Business, Authenticator on iOS/Android) The device’s secure enclave/TPM Knowledge workers on managed devices Lost device == re-registration
Certificate-based authentication (CBA) A PKI-issued cert (often smart card) Regulated / gov, existing PKI Requires PKI + CRL/OCSP plumbing

A pragmatic default for most enterprises: device-bound passkeys (Hello + Authenticator passkeys) as the daily driver for the workforce, hardware security keys for privileged identities and any account that touches the control plane. Reserve CBA for shops that already run a mature PKI; standing one up just for auth is a large project.

One important distinction: prefer device-bound passkeys (key material that cannot be exported) over synced consumer passkeys when you need assurance about where the credential lives. Device-bound keys are what attestation and AAGUID allow-lists in step 5 can actually verify.

2. Enable the methods

Turn on FIDO2 and passkeys in the Authentication methods policy. Do this in Entra admin center -> Protection -> Authentication methods -> Policy, or via Graph. I prefer Graph so the config is reviewable and repeatable.

Connect-MgGraph -Scopes "Policy.ReadWrite.AuthenticationMethod"

# Enable FIDO2 security keys tenant-wide, allow self-service registration
$fido2 = @{
  "@odata.type"        = "#microsoft.graph.fido2AuthenticationMethodConfiguration"
  id                   = "Fido2"
  state                = "enabled"
  isSelfServiceRegistrationAllowed = $true
  isAttestationEnforced = $true
  keyRestrictions      = @{
    isEnforced       = $false   # we tighten this in step 5
    enforcementType  = "allow"
    aaGuids          = @()
  }
}
Update-MgPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration `
  -AuthenticationMethodConfigurationId "Fido2" -BodyParameter $fido2

Passkeys in Microsoft Authenticator are governed by the same Fido2 method configuration plus an Authenticator-specific control surface; enable Authenticator passkeys under the Authenticator method, scoped to the pilot group first. Roll out to a pilot ring (a security group), not “All users,” on day one.

3. Configure authentication strengths and bind them to Conditional Access

Authentication strength is the policy object that says “this resource requires a phishing-resistant method,” independent of which specific keys are enabled. Entra ships three built-in strengths:

Built-in strength Policy ID
Multifactor authentication 00000000-0000-0000-0000-000000000002
Passwordless MFA 00000000-0000-0000-0000-000000000003
Phishing-resistant MFA 00000000-0000-0000-0000-000000000004

The Phishing-resistant MFA strength (...004) accepts exactly three methods: FIDO2 security key, Windows Hello for Business, and certificate-based authentication (multifactor). Phone, SMS, and Authenticator push cannot satisfy it. That is the whole point.

Bind it to Conditional Access. Start with the highest-value target – privileged roles – in report-only, then expand. Always exclude break-glass (step 6).

Connect-MgGraph -Scopes "Policy.ReadWrite.ConditionalAccess","Policy.Read.All"

$params = @{
  displayName = "CA-PR01 - Phishing-resistant MFA for admins"
  state       = "enabledForReportingButNotEnforced"   # report-only first
  conditions  = @{
    users = @{
      includeRoles = @(
        "62e90394-69f5-4237-9190-012177145e10"  # Global Administrator
      )
      excludeUsers = @("<break-glass-1-oid>","<break-glass-2-oid>")
    }
    applications = @{ includeApplications = @("All") }
  }
  grantControls = @{
    operator = "OR"
    authenticationStrength = @{ id = "00000000-0000-0000-0000-000000000004" }
  }
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $params

You can also author a custom authentication strength if you want, say, FIDO2 keys only (excluding Hello) for the most sensitive scope – useful when admins must use portable hardware keys, not the workstation TPM. Custom strengths live under Authentication methods -> Authentication strengths.

Run report-only for one to two weeks. Review Insights & reporting to see who would have been blocked, fix their registration, then flip state to enabled.

4. Registration campaign and Temporary Access Pass onboarding

You cannot enforce a method nobody has registered. Two mechanisms drive adoption.

Registration campaign nudges users at sign-in to set up the recommended passwordless method. Scope it, give it a snooze count, and exclude users who already have a passkey.

$campaign = @{
  registrationEnforcement = @{
    authenticationMethodsRegistrationCampaign = @{
      snoozeDurationInDays = 1
      state                = "enabled"
      excludeTargets       = @()
      includeTargets       = @(
        @{
          id                  = "<pilot-group-oid>"
          targetType          = "group"
          targetedAuthenticationMethod = "microsoftAuthenticator"
        }
      )
    }
  }
}
Update-MgPolicyAuthenticationMethodPolicy -BodyParameter $campaign

Temporary Access Pass (TAP) solves the bootstrap problem: how does a user register their first passkey when you are trying to kill passwords? A TAP is a time-limited, optionally one-time passcode that an admin (or via PIM, a help desk) issues. The user signs in with the TAP and immediately enrolls a security key or passkey – no password ever involved.

# Enable TAP as a method (one-time use, tight lifetime)
$tapConfig = @{
  "@odata.type"         = "#microsoft.graph.temporaryAccessPassAuthenticationMethodConfiguration"
  id                    = "TemporaryAccessPass"
  state                 = "enabled"
  defaultLifetimeInMinutes = 60
  defaultLength         = 8
  isUsableOnce          = $true
  minimumLifetimeInMinutes = 60
  maximumLifetimeInMinutes = 480
}
Update-MgPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration `
  -AuthenticationMethodConfigurationId "TemporaryAccessPass" -BodyParameter $tapConfig

# Issue a one-time TAP to a user onboarding their first passkey
$pass = @{ isUsableOnce = $true; lifetimeInMinutes = 60 }
New-MgUserAuthenticationTemporaryAccessPassMethod -UserId "newhire@contoso.com" -BodyParameter $pass

Hand the TAP out of band (not over the same email you are protecting). For new-hire flows, wire TAP issuance into your joiner process so day-one onboarding is passwordless from first login.

5. Enforce key restrictions with AAGUID allow-lists and attestation

Enabling FIDO2 lets any WebAuthn key register – including cheap, unknown-provenance keys a user bought online. For privileged identities you want to constrain to vetted models. Every authenticator model has a stable AAGUID (Authenticator Attestation GUID). An allow-list pins registration to your approved hardware.

Pair it with attestation enforcement, which validates the key’s attestation statement against the FIDO Metadata Service (MDS) at registration – proving the key really is the make and model it claims, with non-exportable key material.

$fido2Restricted = @{
  "@odata.type"        = "#microsoft.graph.fido2AuthenticationMethodConfiguration"
  id                   = "Fido2"
  state                = "enabled"
  isAttestationEnforced = $true
  keyRestrictions      = @{
    isEnforced      = $true
    enforcementType = "allow"           # only these AAGUIDs may register
    aaGuids = @(
      "ee882879-721c-4913-9775-3dfcce97072a",  # YubiKey 5 Series (example)
      "fa2b99dc-9e39-4257-8f92-4a30d23c4118"   # YubiKey 5 NFC (example)
    )
  }
}
Update-MgPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration `
  -AuthenticationMethodConfigurationId "Fido2" -BodyParameter $fido2Restricted

Get the real AAGUIDs from your vendor’s published list – do not guess. The values above are illustrative. Validate your allow-list against the actual keys in a registration test before enforcing, or you will block your own pilot.

Caution: isAttestationEnforced = $true will reject keys whose attestation can’t be validated against MDS, and some otherwise-fine keys ship without an attestation cert chain Entra recognizes. Test with your exact procurement before turning it on tenant-wide.

A note on schema evolution: Microsoft is migrating these controls into a passkey profile model, and the standalone isAttestationEnforced / keyRestrictions properties on the FIDO2 config are slated for retirement (kept in sync with the new profile during transition). Pin your tooling and re-read the resource type when you automate this, because the property surface is changing.

6. Designing two break-glass accounts

This is the part teams get wrong, and it is the part that ends careers. If a Conditional Access policy or a method outage locks out every admin, break-glass accounts are your way back in.

Build two (so a single account compromise or expiry does not strand you), with these properties:

// Sentinel / Log Analytics: alert on ANY break-glass sign-in
SigninLogs
| where UserPrincipalName in~ ("bg1@contoso.onmicrosoft.com",
                               "bg2@contoso.onmicrosoft.com")
| project TimeGenerated, UserPrincipalName, IPAddress, AppDisplayName,
          ResultType, ResultDescription, ConditionalAccessStatus
| order by TimeGenerated desc

Wire that to an analytics rule with severity High and a real paging action. A break-glass login that nobody noticed is the same as not having monitoring.

Quarterly testing is mandatory. A break-glass account you have never tested is a hypothesis, not a control. Each quarter: log in with each account (which validates the password and the excluded-from-CA path), confirm the alert fired, rotate the password, and record it in the runbook. Calendarize it.

7. Edge cases: shared devices, kiosks, frontline workers, legacy auth

Real estates are messy. Plan these before enforcement, not after.

# Block legacy authentication tenant-wide (separate, single-purpose policy)
$legacy = @{
  displayName = "CA-PR09 - Block legacy authentication"
  state       = "enabledForReportingButNotEnforced"
  conditions  = @{
    users        = @{ includeUsers = @("All"); excludeUsers = @("<break-glass-1-oid>","<break-glass-2-oid>") }
    applications = @{ includeApplications = @("All") }
    clientAppTypes = @("exchangeActiveSync","other")
  }
  grantControls = @{ operator = "OR"; builtInControls = @("block") }
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $legacy

Verify

Do not declare victory off a dashboard tile. Confirm the actual behaviors:

// What authentication method satisfied recent sign-ins?
SigninLogs
| where TimeGenerated > ago(7d)
| where ConditionalAccessStatus == "success"
| mv-expand AuthenticationDetails = todynamic(AuthenticationDetails)
| extend Method = tostring(AuthenticationDetails.authenticationMethod)
| summarize Count = count() by Method
| order by Count desc
# Quick CLI sanity check: who still has no strong (passwordless) method registered?
az rest --method get \
  --url "https://graph.microsoft.com/v1.0/reports/authenticationMethods/userRegistrationDetails?\$filter=isPasswordlessCapable eq false&\$select=userPrincipalName,methodsRegistered" \
  --query "value[].{user:userPrincipalName, methods:methodsRegistered}" -o table

That last query is your enforcement gate: never flip a strength policy from report-only to enabled while the target population still contains users who are not passwordless-capable.

Enterprise scenario

A platform team at a healthcare provider rolled phishing-resistant MFA to ~12,000 staff. Knowledge workers got Authenticator/Hello passkeys; the security plan called for hardware keys on the ~400 privileged accounts. They authored a custom authentication strength (FIDO2 only, no Hello) bound to a CA policy targeting their admin roles, and enabled an AAGUID allow-list for the two YubiKey models procurement had standardized on.

The constraint surfaced on the clinical floor. Nurses moved between shared workstations every few minutes, and the platform passkey design assumed one user per device TPM – which broke the instant a second nurse tried to sign in. Worse, the team had set isAttestationEnforced = $true tenant-wide, and a batch of keys from a third vendor (bought before standardization) failed MDS attestation, so a cohort of clinicians couldn’t register anything during the registration campaign and started snoozing the prompt indefinitely.

The fix had three parts. First, they split the population: shared clinical workstations went to hardware security keys (portable – the key travels with the nurse, not the device) instead of platform passkeys. Second, they moved attestation and key-restriction enforcement off the tenant-wide FIDO2 config and onto the privileged scope only, so the workforce could register approved keys without the strict attestation gate that the off-list third-vendor keys tripped. Third – the load-bearing detail – they had a 02:00 near-miss when a misauthored CA policy briefly blocked the admin group: recovery worked only because both break-glass accounts were excluded from every policy and each had a FIDO2 key in the safe. They logged in, reverted the policy, and the Sentinel rule paged the on-call as designed.

# Privileged-only FIDO2 enforcement: a CUSTOM strength (FIDO2 keys only),
# bound to a CA policy scoped to admin roles -- not the whole tenant.
$strength = @{
  displayName         = "PR - Hardware FIDO2 only"
  allowedCombinations = @("fido2")
}
$created = New-MgPolicyAuthenticationStrengthPolicy -BodyParameter $strength

$caAdmins = @{
  displayName = "CA-PR02 - Hardware FIDO2 for privileged roles"
  state       = "enabled"
  conditions  = @{
    users = @{
      includeRoles = @(
        "62e90394-69f5-4237-9190-012177145e10",  # Global Administrator
        "194ae4cb-b126-40b2-bd5b-6091b380977d"   # Security Administrator
      )
      excludeUsers = @("<break-glass-1-oid>","<break-glass-2-oid>")
    }
    applications = @{ includeApplications = @("All") }
  }
  grantControls = @{
    operator = "OR"
    authenticationStrength = @{ id = $created.Id }
  }
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $caAdmins

The lesson the post-incident review wrote down: scope attestation and key restrictions to where assurance is actually required, segment shared devices to portable keys early, and treat break-glass as the one control you rehearse on a schedule rather than the one you hope you never need.

Rollout checklist

Phishing-resistant passwordless is not a feature toggle; it is a sequenced migration. Enable the methods, prove who can use them, enforce with strengths bound to Conditional Access, and – before any of that – build break-glass you have actually tested. Get the order wrong and you either leave the AiTM hole open or lock yourself out of your own tenant. Get it right and you remove the single largest class of identity attacks from the board.

FIDO2passwordlesspasskeysauthentication-strengthbreak-glassEntra

Comments

Keep Reading