Skip to main content

Authentication flows

You'll most likely need to support other authentication mechanisms alongside passkeys. Most likely because you have a legacy userbase already using username/password based authentication. You need to think about your UI and UX. There are a few ways to run passkeys alongside legacy authentication. We'll call these "authentication flows".

Login with Passkey button

The simplest option, similar to how you might offer a "login with Facebook" option, you offer a passkey login option. This approach works well assuming the user remembers whether they created a passkey. A passkey user who tries to login with their password will either be authenticated using their password (see legacy users) or asked to login with their passkey.

However, the situation is more confusing when a user who doesn't have a passkey, tries to sign in with one. The browser will not display a message telling them that no passkey can be found. Instead it will ask them to select a passkey on a different device. At this point most users get very confused.

If you do adopt this flow, you'll probably want to give your users some guidance explaining that this may happen.

Benefits

  • Simple to implement - You're empowering the user to choose their means of authentication

Drawbacks

  • Poor UX - Users who don't yet have a passkey will certainly try pressing the button and most likely get confused
  • Adoption - The browser may autofill a legacy username/password. Persuading users to select the passkey option could be challenging when as far as they're concerned "passwords work"
Why do browsers do this?

Passkeys are relatively new and include the concept of cloud backup and device sync. i.e. a user can create a passkey on their iPhone, but use the same passkey on their iPad or Macbook. However users might not want to sync their passkey, or the device itself may not support it. In this scenario, the user would only be able to use the passkey on a single device.

To aleviate the situation browsers also offer something known as roaming authenticators - the ability to sign in to a device, using a passkey stored on another device. Unlike a passkey (that's synced), the private key for a roaming authenticator remains on the device. The challenge is sent from the target device to the authenticator, which signs it and sends it back to the target.

That's why browsers prompt users to use a different device when a passkey can't be found locally. In fact they will typically give them the option of using a roaming passkey/authenticator even when a local passkey exists.

The two step process

Our recommended approach. Instead of displaying a username/password form, you prompt the user for their email. They click next at which point you check their account status. If they have a passkey registered you call authenticatePasskey and the browser will prompt them to login with their passkey.

tip

The passkey check can be handled in your frontend or backend. On the frontend call Passlock's isRegistered({ email }) function. You backend can call the /{tenancy}/user/status?email={email} endpoint to check if they have a passkey.

If they don't have a passkey you instead prompt them to enter their password or email a one time login link/code. As a bonus you can take unregistered users straignt onto your registration form, with their email pre-filled.

Benefits

  • Great UX - It's a clear, simple and well understood flow. Anyone with a Gmail account will be familiar with the approach.
  • Suports one time links - One time links/codes are a great way to support users who can't adopt passkeys. Once the user has entered their email, send the code then prompt them to follow the link or enter the code.

Drawbacks

  • It's a two step process 😂 - There's redundancy involved, especially if the user does have a passkey. Passkeys are known as discoverable credentials - in other words the user shouldn't need to enter a username or email to select a passkey, the browser will automatically select it or ask them to choose.
  • Browser autofill - Some browsers (we're looking at you Safari) will typically only autofill a username/email if it's accompanied by a password field.
tip

"Remember my username" - If the user doesn't want to enter their email and the browser won't autofill it, you can do it for them. During the first successful login set a cookie or use local storage to record the user's email then autofill it during subsequent page visits. You'll probably want to make this feature optional to keep the cookie police happy.

Conditional UI

Browser developers could foreee the challenges associated with a mixed passkey/legacy userbase. They developed the concept of autofill aka conditional ui. Essentially you present your users with a legacy username/password form but add a webauthn autocomplete attribute to the username field e.g.

<input type="text" name="username" autocomplete="username webauthn" />

During page load/component mount you call passlock's authenticate function and pass the autofill option:

login.ts
await authenticate({ ... autofill: true })
info

Call authenticate() on page load/mount. There's no need to wait for the user to click a login button.

When the user tries to enter their username or password, the browser will prompt them to login with their passkey instead (if they have one). If they don't have a passkey they will login as normal.

warning

Be aware that some browsers may support passkeys but not autofill. You should either call Passlock's isAutofillSupported function or examine result of the authenticate call to check for errors. If the browser supports passkeys (test with arePasskeysSupported) but not autofill, you'll need to resort to one of the other strategies detailed on this page.

Benefits

  • Slick - Users don't need to remember if they have a passkey and they don't need to enter a username to find out.
  • Industry standard - Conditional UI was developed by browser developers specifically for a mixed userbase.

Drawbacks

  • Browser support - Some browsers support Passkeys but not autofill so you'll need (yet another fallback / workaround).
  • Poor UX for most users - It's just our opinion but we think the user experience is far from ideal (see below).
The problem with conditional ui

You message your userbase evangelising about passkeys. Whenever a user logs in you prompt them to upgrade to a passkey. Many do. Those users now try to sign into your web app but they can't see any passkey option, just the legacy username password form. It's only when they try to use that form they discover that they can use their passkey.

On the other hand, perhaps a user doesn't want to use their passkey (for whatever reason). Unless they somehow delete it from their device they'll be prompted to use it every time they try to use their password.