GM Retired 200,000 Passwords. The Hard Part Was the Helpdesk Script.

GM Retired 200,000 Passwords. The Hard Part Was the Helpdesk Script.

General Motors moved 200,000 employees from password-plus-MFA to passkeys. The case study out today puts a number on that shift — 200,000 employees now use phishing-resistant sign-in — and the number is the least interesting part. The interesting part is everything that happens between “we turned on the Passkey (FIDO2) method” and “the helpdesk stopped getting calls.”

Most of us have passkeys on a slide somewhere. Few have a plan for the day a plant-floor worker’s only credential is gone and they’re locked out of their shift. That gap — bootstrap and recovery — is where large rollouts quietly stall. Here’s the order I’d actually do the work, and where each step bites if you skip it.

1. Map who holds what device before you touch a policy

Passkeys are not one method. Windows Hello for Business lives on a specific corporate PC. A FIDO2 security key is portable across machines. A passkey in Microsoft Authenticator lives on a phone that may or may not be managed. The method you can realistically deploy is a function of the hardware in someone’s hands, and at GM’s scale that means several very different populations: corporate-managed laptops, BYOD, shared and kiosk devices, and mobile-only frontline staff who don’t sit at a desk at all.

Skip this and you’ll write one beautiful policy that works for knowledge workers and strands the 40% of your headcount who share a terminal. Build the population map first. Everything downstream is scoped to it.

2. Enable the method scoped to a pilot group — never “All users”

In the Entra admin center: Protection > Authentication methods > Policies. Enable Passkey (FIDO2) and target it at a named pilot group, not the tenant. Enabling a method doesn’t force anyone to use it; it makes registration possible. That distinction matters, because you want registration open and enforcement closed for weeks, not minutes.

The mistake here is treating “enable” as a launch. It isn’t. It’s the on-ramp. If you flip enforcement in the same change window, you’ve merged two risks that need separate blast radii.

3. Decide platform vs. cross-platform on purpose, not by default

Windows Hello for Business is a platform authenticator: bound to one device, provisioned through Intune/device policy, brilliant for the single-laptop knowledge worker. FIDO2 security keys are cross-platform: one key, many machines, ideal for shared workstations and anyone who roams. Passkeys in Authenticator cover the mobile-first crowd.

Pick per population. The failure mode at scale is picking one and pretending it covers everyone — you provision Windows Hello everywhere and then discover the shift workers on shared terminals have no personal device to bind to, so they can’t register at all. Security keys cost money and logistics; budget for them where they’re the only option, not as an afterthought.

4. Solve the bootstrap problem before you enforce anything

This is the step people skip, and it’s the one that determines whether your rollout is a project or a permanent helpdesk tax. You cannot register a passkey out of thin air. The user needs an existing strong credential or a Temporary Access Pass (TAP) to get their first passkey on the device. Enable TAP under the same Authentication methods > Policies blade and decide its lifetime and one-time-vs-reusable behaviour deliberately.

TAP is both your onboarding rail and, later, your recovery rail. If you don’t have a clean, scriptable way to issue one, every registration failure and every lost device becomes a manual identity-proofing exercise. At 200,000 users that’s not a queue, it’s a department.

5. Set key restrictions and attestation with a light hand

Under the Passkey (FIDO2) method settings you can enforce attestation and restrict which authenticators are allowed by AAGUID. Tempting to lock hard on day one. Resist. Over-tight attestation enforcement silently rejects perfectly good keys whose vendor attestation Microsoft can’t validate, and the user just sees registration fail with no useful reason. AAGUID allow-lists are the right tool if you’ve standardised on specific key models — but confirm the AAGUIDs of the hardware you actually bought, including the Authenticator app’s own AAGUIDs if you’re allowing phone passkeys.

Tighten this after the pilot proves your chosen hardware registers cleanly, not before.

6. Build a phishing-resistant authentication strength and attach it in report-only

Conditional Access is where enforcement actually lives. Go to Protection > Authentication methods > Authentication strengths and use the built-in Phishing-resistant MFA strength (it covers Windows Hello for Business, FIDO2/passkey, and certificate-based auth). Then create a Conditional Access policy under Protection > Conditional Access > Policies that requires it — and set the policy to Report-only first.

Report-only tells you who would have been blocked without blocking them. Run it against your pilot, read the sign-in logs, find the population that has no registered phishing-resistant method, and fix that before you ever move the toggle to On. Note the licensing floor: Conditional Access and custom authentication strengths need Entra ID P1; risk-based conditions need P2. TAP, passkeys, and Windows Hello themselves don’t — but the enforcement layer does.

7. Keep the MFA fallback alive during migration — don’t hard-cut

The migration from password+MFA to passkey is a coexistence period, not a switch. During rollout, both should satisfy access. You narrow the accepted methods as registration climbs. Yank the fallback on day one and every unregistered user is locked out simultaneously, which is how a security win becomes a Sev1 and a rollback.

The sequence that works: enable → drive registration → report-only enforcement → enforce for registered populations group by group → retire weaker methods last. Measure registration coverage per population as your gate, not calendar dates.

8. Measure registration with the report, not with vibes

Before you promote any group from report-only to enforced, pull the actual registration state. This is read-only and safe to run against the whole tenant:

audit.ps1PowerShell
Connect-MgGraph -Scopes "AuditLog.Read.All","UserAuthenticationMethod.Read.All"try {
    # Pagination handled by -All; this is a read, no state change
    $details = Get-MgReportAuthenticationMethodUserRegistrationDetail -All -ErrorAction Stop$details |
        Select-Object UserPrincipalName,
                      IsMfaRegistered,
                      @{ n = 'PasskeyRegistered'
                         e = { $_.MethodsRegistered -contains 'passKeyDeviceBound' -or
                               $_.MethodsRegistered -contains 'passKeyDeviceBoundAuthenticator' } } |
        Export-Csv -Path .\passkey-registration.csv -NoTypeInformationWrite-Host "Exported $($details.Count) records for review." -ForegroundColor Cyan
}
catch {
    Write-Error "Report pull failed: $($_.Exception.Message)"
}

Method-name strings evolve as Microsoft ships changes — verify the current values against your own tenant’s data before you trust the filter. That CSV is your enforcement gate and the input for the next step. Review it by hand. Never pipe a live query straight into a state-changing command.

9. Write the device-loss runbook before you need it

A user whose only passkey was on a phone that’s now in a river is not an edge case at 200,000 users — it’s a daily event. The helpdesk script is the deliverable, and it has three moves: prove identity out-of-band (not over the same channel an attacker would use — this is where help-desk social engineering lands, so make it a real proofing step), revoke the lost credential, and issue a fresh TAP so the user can register a new passkey.

Issue TAPs from a reviewed list, not ad hoc, and default to a dry run:

audit.ps1PowerShell
Connect-MgGraph -Scopes "UserAuthenticationMethod.ReadWrite.All"# Reviewed input only — produced by an approved recovery ticket, not a live query
$recovery = Import-Csv .\approved-recovery.csv   # column: UserPrincipalName$body = @{
    isUsableOnce      = $true
    lifetimeInMinutes = 60
}foreach ($u in $recovery) {
    try {
        $user = Get-MgUser -UserId $u.UserPrincipalName -ErrorAction Stopif ($PSCmdlet.ShouldProcess($user.UserPrincipalName, "Issue Temporary Access Pass")) {
            New-MgUserAuthenticationTemporaryAccessPassMethod `
                -UserId $user.Id -BodyParameter $body -ErrorAction Stop
        }
        else {
            Write-Host "[WhatIf] Would issue TAP to $($user.UserPrincipalName)"
        }
    }
    catch {
        Write-Warning "Skipped $($u.UserPrincipalName): $($_.Exception.Message)"
    }
}

Wrap this in a function with [CmdletBinding(SupportsShouldProcess)] so -WhatIf is the default posture and issuing for real is the deliberate exception. And revoke the lost credential — remove its FIDO2 method and revoke the user’s sign-in sessions — as part of the same ticket, or you’ve handed the finder of that phone a live key.

10. Treat registration failures as a supported state

Attestation rejects, an unsupported browser, a device that can’t do platform authenticators, a shared terminal with no personal binding — registration will fail for a slice of users, and the ones it fails for are disproportionately your frontline and BYOD populations. Have a documented answer for each before enforcement, because “it didn’t work and I don’t know why” is exactly the moment a user asks to just go back to a password.

If you do only one thing from this list, build steps 4 and 9 first: the Temporary Access Pass rail and the recovery runbook. Everything else is Conditional Access configuration you can iterate on in report-only. But a phishing-resistant estate with no bootstrap and no recovery path isn’t more secure — it’s just a lockout waiting for a lost phone. GM’s win wasn’t the passkey. It was having somewhere to put the user when the passkey was gone.