Keep passkeys consistent with credentials on your server with the Signal API

Published: November 12, 2024

The WebAuthn Signal API allows relying parties to signal existing credentials to connected passkey providers. With this, a supporting passkey provider can update or remove incorrect or revoked passkeys from its storage so they are no longer offered to users.

Compatibility

Chrome on desktop supports Signal API starting from Chrome 132. Google Password Manager can update passkeys reflecting the signal. For Chrome extension based passkey providers, it's up to them whether they will reflect the signal or not.

Chrome on Android support is coming later.

Safari is supportive but not implemented yet. Firefox hasn't shared their opinions yet.

Background

When a passkey (a discoverable credential) is created, metadata such as a username and a display name are saved to the passkey provider (such as a password manager) along with the private key, while the public key credential is saved to the relying party's (RP's) server. Saving the username and display name helps the user to identify which of the offered passkeys to sign in with when prompted. This is especially useful when they have more than two passkeys from different passkey providers.

However, there are a couple of cases where inconsistencies between the passkey provider's passkey list and the server's credentials list can lead to confusion.

The first case is when a user deletes a credential on the server leaving the passkey in the passkey provider untouched. The next time the user tries to sign in with a passkey, that passkey will still be presented to the user by the passkey provider. However, the attempt to sign in will fail because the server won't be able to verify with the public key which was deleted.

The second case is when a user updates their username or the display name on the server. The next time the user tries to sign in, the passkey in the passkey provider continues to display the old username and display name despite it's updated on the server. Ideally they should be in sync.

Signal API

The Signal API is a WebAuthn API that resolves these confusions by allowing RPs to signal changes to the passkey provider. There are three methods:

Signal that a credential does not exist

const credential = await navigator.credentials.get({ ... });
const payload = credential.toJSON();

const result = await fetch('/login', { ... });

// Detect authentication failure due to lack of the credential
if (result.status === 404) {
  // Feature detection
  if (PublicKeyCredential.signalUnknownCredential) {
    await PublicKeyCredential.signalUnknownCredential({
      rpId: "example.com",
      credentialId: "vI0qOggiE3OT01ZRWBYz5l4MEgU0c7PmAA" // base64url encoded credential ID
    });
  } else {
    // Encourage the user to delete the passkey from the password manager nevertheless.
    ...
  }
}

By calling PublicKeyCredential.signalUnknownCredential() with an RP ID and a credential ID, the RP can inform the passkey provider that the specified credential has been removed or does not exist. It's up to the passkey provider how to deal with this signal, but the associated passkey is expected to be removed so that the user won't sign in with a passkey as the associated credential does not exist.

This API can be invoked when a passkey based sign-in has failed due to an absence of a credential. This way, the RP can prevent the user from attempting to sign in with a passkey that doesn't have an associated credential. Unlike signalAllAcceptedCredentials, this method does not require passing the entire list of credential IDs, so it should be used whenever the user is not authenticated to avoid revealing the number of passkeys for a given user.

A dialog displayed when a passkey is deleted from Google Password Manager on Chrome.
A dialog displayed when a passkey is deleted from Google Password Manager on Chrome.

Signal a list of saved credentials

// After a user deletes a passkey or a user is signed in.

// Feature detection
if (PublicKeyCredential.signalAllAcceptedCredentials) {
  await PublicKeyCredential.signalAllAcceptedCredentials({
    rpId: "example.com",
    userId: "M2YPl-KGnA8", // base64url encoded user ID
    allAcceptedCredentialIds: [ // A list of base64url encoded credential IDs
      "vI0qOggiE3OT01ZRWBYz5l4MEgU0c7PmAA",
      ...
    ]
  });
}

By calling PublicKeyCredential.signalAllAcceptedCredentials() with an RP ID, a user ID and a list of credential ID of stored credentials, the RP can inform the passkey provider of the remaining credentials in its storage. It's up to the passkey provider how to deal with this signal, but the passkeys that don't match this list are expected to be removed so that the user won't see passkeys on sign in for which the associated credential does not exist.

This API should be invoked when a user deletes a passkey on the RP and on every sign-in, so that the passkey provider can keep a synchronized list of passkeys.

Signal updated username and display name

// After a user updated their username and/or display name
// or a user is signed in.

// Feature detection
if (PublicKeyCredential.signalCurrentUserDetails) {
  await PublicKeyCredential.signalCurrentUserDetails({
    rpId: "example.com",
    userId: "M2YPl-KGnA8", // base64url encoded user ID
    name: "a.new.email.address@example.com", // username
    displayName: "J. Doe"
  });
} else {
}

By calling PublicKeyCredential.signalCurrentUserDetails() with an RP ID, a user ID, a username and a display name, the RP can inform the passkey provider of the updated user information. It's up to the passkey provider how to deal with this signal, but the passkeys the user owns are expected to be updated with the new user information.

This API can be invoked when the user's username or display name are updated, and on every sign in, so that the passkey provider can keep this information synchronized with the server.

A dialog displayed when a passkey metadata is updated in Google Password Manager on Chrome.
A dialog displayed when a passkey metadata is updated in Google Password Manager on Chrome.

Summary

The Signal API helps you build a better passkey experience by eliminating chances of unexpected sign-in failure. With Signal API, relying parties can signal the list of existing credentials and their metadata, so they can keep passkeys on the passkey provider in-sync.

To learn more about passkeys, start from Passwordless login with passkeys.