![Eiji Kitamura](https://web.dev/images/authors/agektmr.jpg) Eiji Kitamura [X](https://twitter.com/agektmr) [GitHub](https://github.com/agektmr) [Mastodon](https://infosec.exchange/@agektmr) [Bluesky](https://bsky.app/profile/agektmr.com) ![Maud Nalpas](https://web.dev/images/authors/maudnalpas.jpg) Maud Nalpas [GitHub](https://github.com/maudnals) [LinkedIn](https://www.linkedin.com/in/maudnalpas) [Bluesky](https://bsky.app/profile/maudnals.bsky.social)

<br />

While FIDO credentials such as passkeys aim to replace passwords, most of them can also free the user from typing in a username. This enables users to authenticate by selecting an account from a list of passkeys they have for the current website.

Earlier versions of security keys were designed as 2-step authentication methods, and required the IDs of potential credentials, thus requiring entry of a username. Credentials that a security key can find without knowing their IDs are called discoverable credentials. Most FIDO credentials created today are discoverable credentials; particularly passkeys stored in a password manager or on a modern security key.

To ensure your credentials are created as passkeys (discoverable credentials), specify [`residentKey` and `requireResidentKey`](https://web.dev/articles/webauthn-discoverable-credentials.md.txt#resident-key) when the credential is created.

Relying parties (RPs) can use discoverable credentials by omitting
[`allowCredentials`](https://web.dev/articles/webauthn-discoverable-credentials.md.txt#allow-credentials) during passkey authentication. In these cases,
the browser or system show the user a list of available passkeys, identified by the
`user.name` property set at creation time. If the user selects one, the `user.id`
value will be included in the resulting signature. The server can then use that or
the returned credential ID to look up the account instead of a typed username.

Account selector UIs, like the ones discussed earlier, never show non-discoverable
credentials.

**Note:** On Google Password Manager for Android, discoverable credentials are synchronized across devices. For now, RPs can explicitly request non-discoverable credentials if they want to create device-bound credentials.

**Note:** All WebAuthn credentials created using iCloud
Keychain (macOS, iOS, or iPadOS) are synced passkeys. RPs can't create
non-discoverable WebAuthn credentials or device-bound passkeys with iCloud
Keychain.

## `requireResidentKey` and `residentKey`

To create a passkey, specify `authenticatorSelection.residentKey` and `authenticatorSelection.requireResidentKey` on `navigator.credentials.create()` with the values indicated as follows.
**Note:** A discoverable credential was previously known as a *resident key*. This legacy term is still used in the WebAuthn API.

```javascript
async function register () {
  // ...

  const publicKeyCredentialCreationOptions = {
    // ...
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      residentKey: 'required',
      requireResidentKey: true,
    }
  };

  const credential = await navigator.credentials.create({
    publicKey: publicKeyCredentialCreationOptions
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;

  // ...
}
```

`residentKey`:

- `'required'`: A discoverable credential must be created. If it can't be created, `NotSupportedError` is returned.
- `'preferred'`: The RP prefers creating a discoverable credential, but accepts a non-discoverable credential.
- `'discouraged'`: The RP prefers creating a non-discoverable credential, but accepts a discoverable credential.

`requireResidentKey`:

- This property is retained for backward compatibility from WebAuthn Level 1, an older version of the specification. Set this to `true` if `residentKey` is `'required'`, otherwise set it to `false`.

**Note:** If no value is given to `residentKey`, the effective value is `'required'` if `requireResidentKey` is set to `true`, `'discouraged'` if `requireResidentKey` is set to `false` or absent.

## `allowCredentials`

RPs can use `allowCredentials` on `navigator.credentials.get()` to control the passkey authentication experience. There are usually three types of passkey authentication experiences:

- [Show a modal account selector](https://web.dev/articles/webauthn-discoverable-credentials.md.txt#modal)
- [Show a passkey form autofill](https://web.dev/articles/webauthn-discoverable-credentials.md.txt#autofill)
- [Reauthentication](https://web.dev/articles/webauthn-discoverable-credentials.md.txt#reauthentication)

### Show a modal account selector

With discoverable credentials, RPs can show a modal account selector for the user to select an account to sign in with, followed by user verification. This is suitable for passkey authentication flow initiated by pressing a button dedicated to passkey authentication.
[Video](https://www.youtube.com/watch?v=BsRRximoOdo)

To achieve this user experience, omit or pass an empty array to `allowCredentials` parameter in `navigator.credentials.get()`.

```javascript
async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // You can omit `allowCredentials` as well:
    allowCredentials: []
  };

  const credential = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions,
    signal: abortController.signal
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}
```

### Show a passkey form autofill

The modal account selector described above works well if most users use passkeys and have them available on the local device. For a user that does not have local passkeys, the modal dialog still appears and will offer the user to present a passkey from another device. While transitioning your users to passkeys, you may want to avoid that UI for users that haven't set one up.

Instead, the selection of a passkey may be folded into autofill prompts for the fields in a traditional sign-in form, alongside saved usernames and passwords. This way, a user with passkeys can "fill" the sign-in form by selecting their passkey, users with saved username/password pairs can select those, and users with neither can still type in their username and password.

This user experience is ideal when the RP is under a migration with a mixed usage of passwords and passkeys.
[Video](https://www.youtube.com/watch?v=6GMDhF1eQOQ)

To achieve this user experience, in addition to passing an empty array to `allowCredentials` property or omitting the parameter, specify `mediation: 'conditional'` on `navigator.credentials.get()` and annotate an HTML `username` input field with `autocomplete="username webauthn"` or a `password` input field with `autocomplete="password webauthn"`.

The call to `navigator.credentials.get()` will not cause any UI to be shown, but if the user focuses the annotated input field, any available passkeys will be included in the autofill options. If the user selects one, they will go through the regular device unlock verification, and only then will the promise returned by `.get()` resolve with a result. If the user doesn't select a passkey, the promise never resolves.

```javascript
async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // You can omit `allowCredentials` as well:
    allowCredentials: []
  };

  const cred = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions,
    signal: abortController.signal,
    // Specify 'conditional' to activate conditional UI
    mediation: 'conditional'
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}
```

```html
<input type="text" name="username" autocomplete="username webauthn" ...>
```

You can learn how to build this user experience at [Sign in with a passkey through form autofill](https://web.dev/articles/passkey-form-autofill.md.txt), as well as the [Implement passkeys with form autofill in a web app](https://developers.google.com/codelabs/passkey-form-autofill) codelab.

### Reauthentication

In some cases, such as when using passkeys for reauthentication, the identifier of the user is already known. In this case, we would like to use a passkey without the browser or OS showing any form of an account selector. This can be achieved by passing a list of credential IDs in the `allowCredentials` parameter.

In that case, if any of the named credentials are available locally, the user is prompted for device unlock straight away. If not, the user is prompted to present another device (a phone or a security key) that holds a valid credential.
**Note:** This is the traditional way WebAuthn was used for older forms of security keys, and will work for both discoverable and non-discoverable credentials. [Video](https://www.youtube.com/watch?v=HQTzepvK3DQ)

To achieve this user experience, provide a list of credential IDs for the signing in user. The RP should be able to query them because the user is already known. Provide credential IDs as `PublicKeyCredentialDescriptor` objects in the `allowCredentials` property in `navigator.credentials.get()`.

```javascript
async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // Provide a list of PublicKeyCredentialDescriptors:
    allowCredentials: [{
      id: ****,
      type: 'public-key',
      transports: [
        'internal',
        'hybrid'
      ]
    }, {
      id: ****,
      type: 'public-key',
      transports: [
        'internal',
        'hybrid'
      ]
    }, ...]
  };

  const credential = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions,
    signal: abortController.signal
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}
```

A `PublicKeyCredentialDescriptor` object consists of:

- `id`: An ID of the public key credential the RP has obtained on the passkey registration.
- `type`: This field is usually `'public-key'`.
- `transports`: A hint of transports supported by the device holding this credential, used by browsers to optimize the UI that asks the user to present an external device. This list, if provided, should contain the result of calling [`getTransports()`](https://w3c.github.io/webauthn/#dom-authenticatorattestationresponse-gettransports) during the registration of each credential.

## Summary

Discoverable credentials make the passkey sign-in experience much more user friendly by allowing them to skip entering a username. With the combination of `residentKey`, `requireResidentKey`, and `allowCredentials`, RPs can achieve sign-in experiences that:

- Show a modal account selector.
- Show a passkey form autofill.
- Reauthentication.

Use discoverable credentials wisely. By doing so, you can design sophisticated passkey sign-in experiences that users will find seamless and be more likely to engage with.