Supporting Passkeys Across Browsers and Platforms
The WebAuthn API is standard; what users actually experience is not. Availability, prompt design, synchronisation behaviour and error reporting all differ by platform, and a passkey rollout that assumes one behaviour produces confusing dead ends on the others. This page is part of the implementing passkeys and WebAuthn guide.
Detect Capabilities, Do Not Sniff Browsers
The platform exposes exactly the questions you need to ask, so never branch on a user-agent string — it is wrong within a release cycle and it misses the cases that matter, such as a desktop browser that can use a phone as an authenticator.
const supported = typeof window.PublicKeyCredential !== "undefined";
const [platformAuthenticator, conditionalUi] = supported
? await Promise.all([
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
PublicKeyCredential.isConditionalMediationAvailable?.() ?? false,
])
: [false, false];
Those three answers drive the whole interface. supported decides whether to offer passkeys at all. platformAuthenticator tells you whether this device can create one itself — a laptop without biometrics may still use a security key or a phone, so a false answer is not “no passkeys”. And conditionalUi decides whether you can offer passkeys in the autofill dropdown rather than behind a button.
Cache the answers per session rather than per page load, and re-evaluate after enrolment, because a user who has just registered a credential should immediately see the passkey path on subsequent visits.
Conditional UI Is the Feature Users Notice
Conditional mediation — the passkey appearing in the username field’s autofill list — is the difference between passkeys feeling like a shortcut and feeling like an extra step. Where it is available, request it on page load and let the browser surface the credential naturally.
The conditional request must be started before the user focuses the field and abandoned cleanly if they type a password instead — use an AbortController and cancel it when another authentication path begins, or the outstanding request can block a later call.
Cross-Device Sign-In Changes the Mental Model
A user on a desktop without a platform authenticator can still authenticate by scanning a QR code with a phone that holds the passkey. That flow is genuinely useful and it changes what “this device does not support passkeys” means — usually it means “this device cannot create one”, not “this user cannot use one here”.
Word your interface accordingly. Offering “use a passkey from another device” rather than hiding the option entirely is the difference between a user completing sign-in and falling back to a password they may not remember.
Expect the flow to be slower and occasionally to fail on restricted networks, since it depends on a proximity check between the two devices. Give it a generous timeout, show clear progress, and always leave the fallback path visible while it runs.
Errors Differ, So Map Them Deliberately
The specification defines a small set of error names, and platforms use them with different emphasis. Mapping them to user-facing messages is what turns a dead end into a next step.
NotAllowedError is deliberately vague — it covers a user dismissing the prompt, a timeout, and several conditions the platform declines to distinguish for privacy reasons. Do not try to infer more from it than it says; offer a retry and keep the alternative path visible.
Enrolment Across Several Devices
Users end up with credentials on more than one device, and how you handle that decides how many support contacts you get.
Send the user’s existing credential identifiers in excludeCredentials during registration so the platform politely declines to enrol the same authenticator twice rather than creating a confusing duplicate. Where the platform reports InvalidStateError, present it as “this device already has a passkey for your account”, not as a failure.
Encourage a second credential immediately after the first, while the concept is fresh, and show a device list with names, platforms and last-used dates. Synced passkeys mean one enrolment often covers several of a user’s devices already — surface that fact, because users who think each device needs its own enrolment give up partway through.
Measuring the Rollout by Platform
Attach the error name to the fallback metric so a platform-specific spike identifies itself. A run of NotAllowedError on one browser usually means the prompt is appearing without enough explanation; a run of SecurityError means a configuration mismatch that only affects the origins that browser is being used on, such as a staging host with a different relying-party identifier.
Watch the second-credential rate as closely as the first. Users with exactly one passkey generate the lockouts and the manual recovery requests, so the share of enrolled users holding two or more is the number that predicts your future support load — and it responds well to a prompt at the right moment rather than to any amount of documentation.
Fallbacks That Do Not Undermine the Point
A passkey rollout is only as strong as what happens when it fails. Keeping a password as the fallback is reasonable during adoption; keeping SMS as the fallback for a phishing-resistant credential is not, because the account’s real strength becomes the weakest path.
Decide the ladder explicitly: passkey first, then another passkey, then a verified recovery path, and only then anything weaker — with the weaker options restricted to regaining access rather than authorising sensitive operations. The recovery strategies guide covers sizing that ladder to the account.
Instrument every step. The share of sign-ins completed with a passkey, the rate of each error name, and the fallback rate per platform together tell you whether the rollout is working — and which platform needs attention — far more reliably than anecdotes from colleagues who all use the same laptop.
Testing on Real Devices
Emulators reproduce the API and not the experience, which is where the differences live. Keep a small device matrix and run the same four checks on each: enrol a credential, sign in with conditional autofill, sign in with the button path, and cancel the prompt to confirm the error handling.
Cover at minimum a recent version of each major desktop browser, one iOS device, one Android device, and one hardware security key on a desktop without a platform authenticator. That last combination catches the assumption that “no platform authenticator” means “no passkeys”, which is the most common way a rollout excludes users unnecessarily.
Repeat the matrix after any change to your relying-party identifier, your origin list, or your enrolment flow — and after major browser releases, since prompt behaviour and conditional-mediation support are exactly the sort of thing that changes between versions without any announcement that reaches your team.
Record the results somewhere shared. Passkey behaviour questions come up repeatedly during a rollout, and a table showing what actually happened on each platform ends arguments that would otherwise be settled by whoever has the loudest anecdote.
Frequently Asked Questions
How do I know whether to show the passkey option?
Ask the platform rather than the user agent: PublicKeyCredential for basic support, isUserVerifyingPlatformAuthenticatorAvailable() for a built-in authenticator, and isConditionalMediationAvailable() for autofill. Remember that a false answer to the second does not mean no passkeys — the user may have one on a phone and use cross-device sign-in, so keep that option offered rather than hidden.
Why does the same code behave differently on iOS and Android?
Because the platform owns the prompt, the credential store and much of the error reporting. Timeouts, whether a credential syncs, and how a cancellation is reported all vary. Write to the specification, handle the documented error names, and test on real devices for each platform you support — an emulator will not reproduce the prompt behaviour that users actually meet.
Should I request attestation to identify the authenticator?
For consumer products, no. It adds a privacy-sensitive prompt on some platforms, complicates the ceremony, and yields information you will not act on. Enterprise deployments restricting authentication to approved hardware are the genuine case, and there you verify the statement against the metadata service and accept that personal devices will be refused.
What if a user's passkey does not appear on a new device?
Either the credential is device-bound rather than synced, or the platform’s keychain has not synchronised. Show whether each credential is backed up in your device list so the user can see which of their passkeys travel, and make enrolling an additional one on the new device a single obvious action rather than a support conversation.
Can I require a passkey and remove passwords entirely?
Eventually, and only for accounts with at least two credentials or a verified recovery path. Removing the password is a one-way door for users whose devices break, so gate it on evidence that the account can still be recovered, and keep the option to re-add a password if a user’s circumstances change. Adoption first, enforcement second.
Related
- Implementing passkeys and WebAuthn — the ceremonies and server-side verification behind this interface work.
- Passkey account recovery and fallback strategies — sizing the ladder below the passkey.
- Verifying passkey authentication assertions — the checks that make an assertion trustworthy.