This kicks off Field Gotchas — a spin-off of our Phishing-Resistant MFA series.
The original four parts cover how to deploy FIDO2 correctly. Field Gotchas covers the weird, delayed, "but it worked last week" failures that show up after you've done everything right.


Executive Summary

  • After a clean passwordless/FIDO2 rollout, users start losing M365 access days or weeks later — long after go-live
  • A password reset "fixes" it instantly… then it comes back for the next user. Everyone blames the security key
  • Root cause isn't FIDO2 — the account still has a password with an expiration timer; when it expires it enters a credential change-required state that blocks normal token issuance, so the passwordless sign-in workflows that depend on it start failing
  • Fix: stop the password from expiring — DisablePasswordExpiration for cloud-only accounts; SCRIL (or non-expiring policy) for hybrid AD-synced accounts
  • This is a clock, not an event — it surfaces on a delay, which is exactly why it's so confusing to diagnose

The Ticket

Three weeks after a smooth FIDO2 rollout — keys enrolled, zero-lockout wave plan executed, everyone happily tapping their YubiKey to sign in — the tickets started:

"Outlook keeps asking me to sign in and won't take my security key. It worked fine last week. I didn't change anything."

Then another. Then a third, from a different department. The help desk reset the user's password, and the problem vanished instantly. Case closed — until the same symptom showed up on someone else a few days later.

The pattern that emerged was maddening:

  • Passwordless sign-in still worked for most things
  • But certain M365 surfaces — Outlook reconnecting, a Teams token refresh, an app re-consent — would fail
  • A password reset always fixed it
  • It kept coming back, on different users, on a rolling basis

Everyone's first instinct was the same: the FIDO2 deployment broke something. It didn't. We'd been chasing the wrong layer.


The Root Cause Nobody Warns You About

Going passwordless changes how users sign in. It does not delete their password.

When you enroll FIDO2 keys, the underlying account — cloud or on-prem — still has a password object, and that password is still governed by whatever expiration policy is in place (the classic 90-day timer, in most tenants that never turned it off). Your users just stopped typing it, so nobody notices it's still ticking.

Then the timer hits zero.

When a password expires, Entra ID flags the account as "password change required." That flag is the problem. Interactive passwordless sign-in may still succeed in some flows — which is why the symptom is partial and confusing — but any auth path that validates the password state or expects to force a change runs into the change-required block and fails:

  • Token refreshes that fall back to a password check
  • Apps and clients re-authenticating after a cache expiry
  • Anything that tries to surface the "you must change your password" prompt in a flow that can't display it

The user, who hasn't typed a password in three weeks and signs in with a tap, has no idea their password just expired — and no obvious way to act on a change-required state they never see.

A password reset clears the change-required flag and resets the expiration clock. That's why the help desk's reset "fixes" it. It also resets the timer — so the same user is fine for another 90 days, and the next user whose clock runs out becomes the next ticket.

A precise way to say it: the account entered a credential state that required password remediation, which prevented normal token issuance — and the passwordless sign-in workflows that depend on that token issuance failed as a consequence. The authenticator never broke. The credential state behind it did.

This is not a FIDO2 bug. It's a password-lifecycle policy that nobody turned off when the passwords stopped being used.

The architectural point most admins miss: passwordless does not necessarily mean password-free. In many hybrid environments, the password still exists, still ages, and can still participate in account-lifecycle decisions even when users never type it. FIDO2, Windows Hello for Business, and smart cards are passwordless sign-in methods — but the identity underneath still carries a password credential governed by lifecycle policy (in hybrid, by on-prem AD rules). "Passwordless" describes the front door, not the account.

This is documented, not anecdotal. Microsoft's own guidance on passwordless security-key sign-in to on-premises resources spells it out under Known behavior:

"If your password has expired, signing in with FIDO is blocked. The expectation is that users reset their passwords before they can log in by using FIDO. This behavior also applies to hybrid on-premises synced user sign-in with Windows Hello for Business cloud kerberos trust."
Microsoft Learn: Passwordless security key sign-in to on-premises resources

So the core mechanism isn't a theory — it's expected behavior, straight from the vendor. What varies (and what you should still verify in your tenant) is which downstream M365 surface trips first, and whether your accounts are cloud-only or hybrid-synced.


Why It Surfaces on a Delay

This is the part that makes it so hard to pin down: the failure is decoupled in time from the change that caused it.

Day 0:    FIDO2 keys enrolled. Everyone signs in by tap. Passwords untouched but still ticking.
Day 1-60: Everything works. Rollout declared a success. Team moves on.
~Day 90:  First user's password hits expiration → change-required → first ticket.
Day 90+:  Users expire on a rolling basis (whenever each last set their password) → tickets trickle in.

By the time the symptoms appear, the FIDO2 project is closed, the change window is a distant memory, and nobody connects "Outlook won't take my key today" to "we went passwordless three weeks ago." The rolling cadence — driven by each user's individual last-password-set date — makes it look random.

It isn't random. It's a population of expiration timers you forgot were running.


Confirming It

Before you change anything, confirm the account is actually in a password-expiry state rather than chasing a different auth problem.

For a cloud-only account, check the password policy and last-set date:

# Microsoft Graph PowerShell
Get-MgUser -UserId user@contoso.com `
  -Property DisplayName,PasswordPolicies,LastPasswordChangeDateTime |
  Select DisplayName, PasswordPolicies, LastPasswordChangeDateTime

If PasswordPolicies does not contain DisablePasswordExpiration, and LastPasswordChangeDateTime is ~90 days ago (or whatever your policy is), that account is on the clock.

In the sign-in / audit logs, a change-required interrupt shows up as a sign-in that succeeds the strong-auth step but is then interrupted for a credential/password action — not a FIDO2 failure. The tell is that the method worked; the account state blocked the flow.

The core behavior is documented (see the Microsoft Learn citation above) — but verify the specifics in your tenant: which downstream M365 surface fails first varies by client and by whether the account is cloud-only or hybrid-synced. Confirm against your sign-in logs before acting on a specific user.


Before You Reset the User

The reset is the reflex — but it only resets the clock, so the ticket comes back. Run this checklist first; if most answers point the same way, you're looking at password lifecycle, not the authenticator:

  • [ ] Is password expiration still active on the account (i.e., not set to never-expire / DisablePasswordExpiration)?
  • [ ] Is the user cloud-only or hybrid (AD-synced)? — this decides which fix applies
  • [ ] For hybrid: is SCRIL enabled on the AD account?
  • [ ] Any recent password-expiration events in the audit log?
  • [ ] Do the sign-in logs show a password-change / credential-remediation requirement — rather than an authentication-method failure?
  • [ ] Does a password reset immediately restore access? (the signature symptom)

That last one is the giveaway: if a reset reliably fixes it and it reliably comes back, you're chasing a lifecycle timer, not a broken key.


The Fix

The durable fix is to stop the password from expiring — because in a passwordless world, the password is a credential of last resort, not a thing users rotate. Which command you run depends on where the account's password policy actually lives.

Cloud-only accounts: disable password expiration

For accounts mastered in Entra ID (not synced from on-prem AD), set the password to never expire:

# Per user
Update-MgUser -UserId user@contoso.com -PasswordPolicies "DisablePasswordExpiration"

# Caution: this is a per-user attribute. For a group, iterate the membership.

This removes the expiration timer while leaving the password in place as a break-glass / recovery credential. Combined with phishing-resistant MFA, a non-expiring strong password that's never interactively used is a reasonable posture — NIST stopped recommending forced periodic rotation years ago.

Hybrid / AD-synced accounts: a group-scoped policy — not a GPO

If your accounts sync from on-prem Active Directory, Entra is not where the password policy lives — AD is. Setting cloud DisablePasswordExpiration on a synced account is the wrong layer. And here's the gotcha that bites people first:

A GPO cannot exempt a group from password expiration. Password age for domain accounts is governed only at the domain root (the Default Domain Policy, applied to all domain users). A GPO linked to an OU or security-filtered to a group only affects local SAM accounts on machines — not domain-account password age. Reach for a GPO here and you'll quietly change nothing.

The right tool is a Fine-Grained Password Policy (PSO):

  1. Create a PSO with no expiration (MaxPasswordAge = 0).
  2. Apply it to the same security group you already use to scope FIDO2 / Conditional Access — so enrolling a user into passwordless automatically exempts them from expiration. One membership, two effects.
# Create a no-expiration Fine-Grained Password Policy and bind it to the FIDO2 group
New-ADFineGrainedPasswordPolicy -Name "FIDO2-NoExpire" -Precedence 10 `
  -MaxPasswordAge ([TimeSpan]::Zero) -ComplexityEnabled $true -MinPasswordLength 14
Add-ADFineGrainedPasswordPolicySubject -Identity "FIDO2-NoExpire" -Subjects "FIDO2-Enrollment-Group"

# Verify: lowest Precedence wins if a user is in multiple PSO groups
Get-ADUserResultantPasswordPolicy -Identity jdoe

[TimeSpan]::Zero (or 0) = never expires; the string "00:00:00" is version-dependent — confirm with Get-ADFineGrainedPasswordPolicy FIDO2-NoExpire | Select MaxPasswordAge. A per-user Set-ADUser -PasswordNeverExpires $true is a fine one-off to unblock a confirmed account, but the PSO-on-the-group is what scales and self-applies as you enroll more users.

Future-state: SCRIL

For the long-term passwordless posture, SCRIL (Smart Card Required for Interactive Logon) is the cleanest answer. With SCRIL set, AD generates a long random password the user never knows or types — eliminating the knowledge factor (and the human-rotation problem) entirely. For a passkey/PIV/FIDO2 population it's a better end-state than a never-expiring password.

Set-ADUser -Identity jdoe -SmartcardLogonRequired $true

Caveat: validate password-rotation behavior at your domain functional level before rolling SCRIL wide (older configs could leave a stale NTLM hash). Pilot in a test OU first.

Then: decide it as policy, not per-user

Whichever path applies, make it a standard step in your passwordless runbook, not a per-ticket fix:

  • Define which population goes passwordless
  • For that population, set the appropriate non-expiry / SCRIL posture at enrollment time
  • Document it so the next admin doesn't rediscover this the hard way in 90 days

The Real Lesson

Passwordless changes the front door. It doesn't touch the plumbing behind it — and the password-expiration timer is plumbing that's been running in every tenant for years.

The failure mode here isn't exotic. It's a default policy that was perfectly sensible in a password world and quietly became a liability the moment your users stopped typing their passwords. The danger is entirely in the delay: nothing breaks at go-live, the project closes, and then weeks later a slow drip of "my key stopped working" tickets shows up with no obvious connection to the change that caused them.

If you're rolling out FIDO2 — or you already have — add one line to the checklist: decide what happens to the password lifecycle for passwordless users, before the first timer runs out.


What Breaks Next

Not every passwordless failure is the authenticator's fault. This one was the password lifecycle. The next one is Conditional Access. Gotcha #2: Error 53003 — "Your passkey doesn't meet the criteria." Except it does — the passkey is fine; a competing Conditional Access policy is blocking token issuance, and the error points you at exactly the wrong place. We'll walk the misdiagnosis and the five-minute fix.

Phishing-Resistant MFA: Field Gotchas — Gotcha #2 (coming next)


This post documents a pattern observed across production passwordless deployments. Environment details have been generalized. Exact symptoms vary by client and auth flow — validate against your own sign-in logs and password policy before acting. The fix differs for cloud-only vs. hybrid accounts; apply the one that matches where your password policy actually lives.


Series:

Want more patterns like this?

Get the full 6-part guide — what Intune doesn't tell you, but you'll hit in production.

Running into this in production? 30 minutes — I will tell you directly if it is worth fixing.

Book a Fit Call →