Skip to content

Passkey patterns and best practice

Passkeys are increadibly powerful and flexible.

They can be used as a primary means of authentication, replacing passwords. They can be used as a second authentication factor, replacing TOTP based authenticators (e.g. Google Authenticator) or single use codes.

They can also be used to confirm a specific operation e.g. starting a subscription, changing billing details or deleting an account. User verification allows you to demonstrate that the device owner authorised a specific operation.

This guide covers the various scenarios in which you might want to use passkeys, together with patterns and best practices.

Just some terms we’ll use throughout the rest of this document:

  • Primary authentication - A mechanism by which a user first identifies themselves to your system and proves they are who they claim to be. e.g. a username/password form.

  • Secondary authentication - A mechanism by which a user further proves they are who they claim to be, typically performed using a different form of authentication (factor) e.g. a TOTP based authenticator or single use email code.

  • Step up authentication - A.K.A. re-authentication. The user is already authenticated but you want to re-authenticate them again because they’re trying to perform a sensitive operation e.g. changing billing details.

  • Additional authenticator - The user has a primary means of authentication but you want to allow them to sign in using another mechanism. A good example is social login - the user could sign in using their Google account or a local username/password.

  • Single step authentication - The user makes a claim and offers their credentials in one operation e.g. a username/password form.

  • Two step authentication - First the user is prompted for their username, then they are prompted for their credentials (password, one time code, passkey etc).

The simplest scenario, but in practice this pattern is rarely implemented as you probably need to account for legacy users that don’t have passkeys, or devices without passkey support.

Primary authentication using passkeys Primary authentication using passkeys

As passkeys are discoverable you can simply ask the device to present a passkey, then lookup the user id based on the verified passkey id:

frontend.ts
// we dont need to pass a username or user id here,
// the browser/device will prompt the user to select
// a passkey, or preselect it if they have only one
await authenticatePasskey({ tenancyId });

This is a good choice when you need to support multiple authentication mechanisms, for example a “legacy” app that allows users to sign in using a username/password. Until the user has presented their username, you don’t know which authentication mechanisms are available to them.

Primary two step authentication using passkeys Primary two step authentication using passkeys

then…

Primary two step authentication using passkeys Primary two step authentication using passkeys

First, collect the user’s username or other identifier (email, cell phone), then lookup their record. If the account has a passkey call authenticatePasskey or authenticatePasskeyUnsafe and pass the id(s) via the allowCredentials option:

frontend.ts
// passkeyId associated with the account the user wants to sign in with
const allowCredentials = [passkeyId];
await authenticatePasskey({ tenancyId, allowCredentials });

If the account does not yet have any associated passkeys, fallback to a valid mechanism e.g. display a password form or send a one time code or link to their device.

Once they have successfully authenticated it’s a good idea to prompt them to add a passkey. Alternatively you can be a bit less obtrusive by displaying a banner at the top of your app.

In this scenario you want the user to authenticate with their passkey in addition to their primary mechanism.

Primary two step authentication using passkeys Primary two step authentication using passkeys

then…

Primary two step authentication using passkeys Primary two step authentication using passkeys

Authenticate the user as usual then prompt them to authenticate with their passkey. As with two step authentication, you should lookup their associated passkey(s) and reference them in the allowCredentials property:

frontend.ts
// passkeyId associated with the account the user wants to sign in with
const allowCredentials = [passkeyId];
await authenticatePasskey({ tenancyId, allowCredentials });

For those user that have not yet registered a passkey, you should fallback to another secondary authentication mechanism e.g. TOTP authentication.

Basically the same as above, although you probably want to enforce user verification / local re-authentication.

In this case the user is already authenticated so you can lookup their associated passkey(s) then pass to authenticatePasskey:

frontend.ts
// passkeyId associated with the account the user wants to sign in with
const allowCredentials = [passkeyId];
// the process will fail if they do not re-authenticate locally
const userVerification = "required" as const;
await authenticatePasskey({ tenancyId, allowCredentials, userVerification });

See user verification for more information.

The user could sign in with their passkey or a different mechanism.

Primary two step authentication using passkeys Primary two step authentication using passkeys

It’s essentially the same as the typical social sign in flow, you present the username/password form with “a sign in with passkey” option.