Implement passkeys with form autofill in a web app

1. Before you begin

The use of passkeys instead of passwords is a great way for websites to make their user accounts safer, simpler, and easier to use. With a passkey, a user can sign in to a website or an app by using the device's screen lock feature, such as a fingerprint, face, or device PIN. A passkey has to be created, associated with a user account, and have its public key stored on a server before a user can sign in with it.

In this codelab, you turn a basic form-based username and password sign-in into one that supports passkeys and includes the following:

  • A button that creates a passkey after the user signs in.
  • A UI that displays a list of registered passkeys.
  • The existing sign-in form that lets users sign in with a registered passkey through form autofill.

Prerequisites

What you'll learn

  • How to create a passkey.
  • How to authenticate users with a passkey.
  • How to let a form suggest a passkey as a sign-in option.

What you'll need

One of the following device combinations:

  • Google Chrome with an Android device that runs Android 9 or higher, preferably with a biometric sensor.
  • Chrome with a Windows device that runs Windows 10 or higher.
  • Safari 16 or higher with an iPhone that runs iOS 16 or higher, or an iPad that runs iPadOS 16 or higher.
  • Safari 16 or higher or Chrome with an Apple desktop device that runs macOS Ventura or higher.

2. Get set up

In this codelab, you use a service called Glitch, which lets you edit client and server-side code with JavaScript, and deploy it solely from the browser.

Open the project

  1. Open the project in Glitch.
  2. Click Remix to fork the Glitch project.
  3. In the navigation menu at the bottom of Glitch, click Preview > Preview in a new window. Another tab opens in your browser.

The Preview in a new window button in the navigation menu at the bottom of Glitch

Examine the website's initial state

  1. In the preview tab, enter a random username and then click Next.
  2. Enter a random password and then click Sign-in. The password is ignored, but you're still authenticated and land on the home page.
  3. If you want to change your display name, do so. That's all you can do in the initial state.
  4. Click Sign out.

In this state, users must enter a password every time that they log in. You add passkey support to this form so that users can sign in with the device's screen-lock functionality. You can try the end state at https://passkeys-codelab.glitch.me/.

For more information about how passkeys work, see How do passkeys work?.

3. Add an ability to create a passkey

To let users authenticate with a passkey, you need to give them the ability to create and register a passkey, and store its public key on the server.

A passkey user verification dialog appears upon passkey creation.

You want to allow the creation of a passkey after the user logs in with a password, and add a UI that lets users create a passkey and see a list of all registered passkeys on the /home page. In the next section, you create a function that creates and registers a passkey.

Create the registerCredential() function

  1. In Glitch, navigate to the public/client.js file and then scroll to the end.
  2. After the relevant comment, add the following registerCredential() function:

public/client. js

// TODO: Add an ability to create a passkey: Create the registerCredential() function.
export async function registerCredential() {

  // TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.

  // TODO: Add an ability to create a passkey: Create a credential.

  // TODO: Add an ability to create a passkey: Register the credential to the server endpoint.

};

This function creates and registers a passkey on the server.

Obtain the challenge and other options from the server endpoint

Before a passkey is created, you need to request parameters to pass in WebAuthn from the server, including a challenge. WebAuthn is a browser API that lets a user create a passkey and authenticate the user with the passkey. Luckily, you already have a server endpoint that responds with such parameters in this codelab.

  • To obtain the challenge and other options from the server endpoint, add the following code to the registerCredential() function's body after the relevant comment:

public/client.js

// TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.
const options = await _fetch('/auth/registerRequest');

The following code snippet includes sample options that you receive from the server:

{
  challenge: *****,
  rp: {
    id: "example.com",
  },
  user: {
    id: *****,
    name: "john78",
    displayName: "John",
  },  
  pubKeyCredParams: [{
    alg: -7, type: "public-key"
  },{
    alg: -257, type: "public-key"
  }],
  excludeCredentials: [{
    id: *****,
    type: 'public-key',
    transports: ['internal', 'hybrid'],
  }],
  authenticatorSelection: {
    authenticatorAttachment: "platform",
    requireResidentKey: true,
  }
}

The protocol between a server and a client isn't part of the WebAuthn specification. However, this codelab's server is designed to return a JSON that's as similar as possible to the PublicKeyCredentialCreationOptions dictionary that's passed to the WebAuthn navigator.credentials.create() API.

The following table isn't exhaustive, but it contains the important parameters in the PublicKeyCredentialCreationOptions dictionary:

Parameters

Descriptions

challenge

A server-generated challenge in an ArrayBuffer object for this registration. This is required but unused during registration unless doing attestation—an advanced topic that isn't covered in this codelab.

user.id

A user's unique ID. This value must be an ArrayBuffer object that doesn't include personal identity information, such as e-mail addresses or usernames. A random, 16-byte value generated per account works well.

user.name

This field should hold a unique identifier for the account that's recognizable by the user, such as their email address or username. It's displayed in the account selector. (If you use a username, use the same value as in password authentication.)

user.displayName

This field is an optional, user-friendly name for the account. It doesn't need to be unique and could be the user's chosen name. If your website doesn't have a suitable value to include here, pass an empty string. This might be displayed on the account selector depending on the browser.

rp.id

A relying party (RP) ID is a domain. A website can specify either its domain or a registrable suffix. For example, if an RP's origin is https://login.example.com:1337, the RP ID can be either login.example.com or example.com. If the RP ID is specified as example.com, the user can authenticate on login.example.com or on any other subdomains of example.com.

pubKeyCredParams

This field specifies the RP's supported public-key algorithms. We recommend setting it to [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]. This specifies support for ECDSA with P-256 and RSA PKCS#1, and supporting these gives complete coverage.

excludeCredentials

Provides a list of already registered credential IDs to prevent registration of the same device twice. If provided, the transports member should contain the result of calling the getTransports() function during the registration of each credential.

authenticatorSelection.authenticatorAttachment

Set to a "platform" value. This indicates that you want an authenticator that's embedded in the platform device so the user won't be prompted to insert something like a USB security key.

authenticatorSelection.requireResidentKey

Set to a Boolean true value. A discoverable credential (resident key) can be used without the server having to provide the ID of the credential and so is compatible with autofill.

authenticatorSelection.userVerification

Set to a "preferred" value or omit it because it's the default value. This indicates whether a user verification that uses the device's screen lock is "required", "preferred", or "discouraged". Setting to a "preferred" value requests user verification when the device is capable.

Create a credential

  1. In the