Skip to main content

Passkeys

Passkeys represent a compelling WebAuthn-based alternative to the timeless combination of "password + second-factor" that we all suffer through.

Passkeys are resistant to push-phishing, are unique across every website, and are all generated using cryptographically secure hardware.

Additionally, passkeys generated by the three main platform authenticator vendors (Apple, Google, and Microsoft) are automatically synchronized across a user's devices by their respective cloud account. That means there's finally an easy way for users to regain account access if they happen to lose or trade in their device. There's never been a better time to update your authentication to use WebAuthn.

The following options are ones you should set when calling SimpleWebAuthn's methods to ensure that your site is ready for passkeys.

Prerequisites

Below are the three platform authenticator vendors and known minimum versions of their software needed for full passkey support:

Apple: iOS 16, macOS 13 (Ventura, Safari-only)

Google: TBD

Microsoft: TBD

FIDO2 security keys are unaffected. They will continue to produce credentials that are hardware bound, and most already support discoverable credentials.

Note that "passkey support" is still "WebAuthn support". Passkeys do not represent a breaking change in how WebAuthn is invoked. Rather they are WebAuthn credentials that can have their private key material synchronized across devices outside the influence of the WebAuthn API. The options below are optimizations for those Relying Parties that want to aim for passkey support as they become more common.

Server

The high-level strategy here is to instruct the authenticator to do the following during registration and authentication:

  1. Generate a discoverable credential. The authenticator must generate and internally store a credential mapped to (rpID + userID).
  2. Perform user verification. The authenticator must provide two authentication factors within a single authenticator interaction.

generateRegistrationOptions()

import { generateRegistrationOptions } from '@simplewebauthn/server';

const options = generateRegistrationOptions({
// ...
authenticatorSelection: {
residentKey: 'required',
userVerification: 'required',
},
});

"Discoverable credentials" used to be called "resident keys". The old name persists in the options passed to navigator.credentials.create().

Regarding Android

Android does not currently support the creation of discoverable credentials, and residentKey: 'required' will cause all current Android devices to fail WebAuthn registration. Support for this and other modern FIDO2 capabilities will launch as part of Google's own passkeys implementation sometime later this year.

verifyRegistrationResponse()

const verification = verifyRegistrationResponse({
// ...
requireUserVerification: true,
});

Make sure to save the transports value returned from @simplewebauthn/browser's startRegistration() method too. Advanced WebAuthn functionality like cross-device auth (i.e. authenticating into a website displayed in Chrome on Windows by using your iPhone) is hard to design good UX around. You can use the browser to figure out when it is available by including each credential's transports in the allowCredentials array passed later into generateAuthenticateOptions(). They will help the browser figure out when a credential might enable a user to log in using new technology that wasn't available before.

Signs that a passkey were created include the following values returned from this method:

  • verification.registrationInfo.credentialDeviceType: 'multiDevice' means the credential will be backed up for use on multiple devices within the same platform vendor cloud ecosystem (e.g. only for use on Apple devices sharing an iCloud account)
  • verification.registrationInfo.credentialBackedUp: true means the private key material has been backed up to the user's cloud account. For all intents and purposes this will always be true when credentialDeviceType above is 'multiDevice'

These values can be stored in the database for a given credential for later reference, to help with understanding rate of adoption of passkeys by your users and adjust your UX accordingly.

generateAuthenticationOptions()

const options = generateAuthenticationOptions({
// ...
userVerification: 'required',
});

verifyAuthenticationResponse()

const authVerify = verifyAuthenticationResponse({
// ...
requireUserVerification: true,
});

Browser

There isn't a whole lot that changes in how you call the browser methods if you want to support passkeys, as passkeys don't involve any changes in how WebAuthn is ultimately invoked.

startRegistration()

No changes are required.

startAuthentication()

No changes are required.

...Unless you are interested in leveraging a new capability coming to WebAuthn in almost all modern browsers that's known as "Conditional UI". Conditional UI gives the browser a chance to find and suggest to the user credentials that they can select to then present to you for authentication, all via the browser's native autofill API.

If this interests you, then please see the Conditional UI section of the docs for startAuthentication() as there is a bit of setup required to get it all working.