> ## Documentation Index
> Fetch the complete documentation index at: https://docs.authsignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Passkeys

> Use the Web SDK to implement passkeys.

<Info>
  In addition to the reference documentation below, check out our end-to-end guide on [how to
  implement passkeys in web apps using Authsignal](/authentication-methods/passkey/web-sdk).
</Info>

## Creating a passkey

Creating a passkey must be authorized by [presenting a challenge with an existing method](/advanced-usage/authenticator-binding#presenting-a-challenge-with-an-existing-method) or [tracking an action to obtain a short-lived token](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token).

```ts theme={null}
const response = await authsignal.passkey.signUp({
  token: "eyJhbGciOiJ...",
  username: "jane.smith@authsignal.com",
  displayName: "Jane Smith",
});
```

### Parameters

<ResponseField name="token" type="string">
  A short-lived token obtained by [tracking an
  action](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token).
</ResponseField>

<ResponseField name="username" type="string">
  The primary user identifier associated with the passkey, e.g. the user's email address.
</ResponseField>

<ResponseField name="displayName" type="string">
  An optional secondary user identifier which the browser may display in place of or alongside the
  username, e.g. the user's full name.
</ResponseField>

<ResponseField name="authenticatorAttachment" type="'platform' | 'cross-platform' | null">
  The preferred authenticator type. Defaults to `platform`, which targets the device's built-in
  authenticator (e.g. Touch ID or Windows Hello). Set to `cross-platform` to target a roaming
  authenticator such as a security key, or `null` to let the user choose.
</ResponseField>

<ResponseField name="hints" type="string[]">
  An array of [WebAuthn credential hints](https://www.w3.org/TR/webauthn-3/#enum-hints) which guide
  the browser's UI, e.g. `["client-device"]`, `["security-key"]`, or `["hybrid"]`.
</ResponseField>

<ResponseField name="useAutoRegister" type="boolean">
  Whether to use the automatic passkey upgrade flow. If true, the user's password manager will be
  prompted to automatically create a passkey after successful authentication via password autofill.
</ResponseField>

<ResponseField name="useCookies" type="boolean">
  Whether to use cookies to bind the session to the browser. Defaults to `false`. Requires a [custom
  API domain](/advanced-usage/custom-api-domains) on the same parent domain as your site.
</ResponseField>

<ResponseField name="syncCredentials" type="boolean">
  Whether to keep the browser's credential manager in sync with the credentials stored by Authsignal.
  Defaults to `true`. See [Credential syncing](#credential-syncing) for more details.
</ResponseField>

### Response

<ResponseField name="response" type="AuthsignalResponse<SignUpResponse>">
  <Expandable title="properties">
    <ResponseField name="data" type="SignUpResponse">
      <Expandable title="properties">
        <ResponseField name="token" type="string">
          A new token which can optionally be used to [bind another
          authenticator](#authenticator-binding) for the user.
        </ResponseField>

        <ResponseField name="userAuthenticator" type="object">
          Only present on first time verification e.g. enrollment.

          <Expandable title="properties">
            <ResponseField name="userAuthenticatorId" type="string" required>
              The ID of the Authsignal user authenticator.
            </ResponseField>

            <ResponseField name="verificationMethod" type="'SMS' | 'AUTHENTICATOR_APP' | 'EMAIL_MAGIC_LINK' | 'EMAIL_OTP' | 'PASSKEY' | 'PUSH' | 'SECURITY_KEY' | 'VERIFF' | 'IPROOV' | 'IDVERSE' | 'PALM_BIOMETRICS_BR'" required />

            <ResponseField name="createdAt" type="string" required>
              The date and time the authenticator was created in ISO 8601 format.
            </ResponseField>

            <ResponseField name="verifiedAt" type="string" required>
              The date and time the authenticator was first verified in ISO 8601 format.
            </ResponseField>

            <ResponseField name="lastVerifiedAt" type="string">
              The date and time the authenticator was last verified in ISO 8601 format.
            </ResponseField>

            <ResponseField name="isDefault" type="boolean" required>
              Whether the authenticator is the default authenticator for the user.
            </ResponseField>

            <ResponseField name="email" type="string">
              The email address of the authenticator. Only present for email authenticators e.g. email OTP and email magic link.
            </ResponseField>

            <ResponseField name="phoneNumber" type="string">
              The phone number of the authenticator. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
            </ResponseField>

            <ResponseField name="previousSmsChannel" type="SMS | WHATSAPP">
              The previous SMS channel used to send the OTP code. Only present for phone authenticators e.g. SMS/WhatsApp OTP.
            </ResponseField>

            <ResponseField name="webauthnCredential" type="string">
              Only present for passkey authenticators.

              <Expandable title="properties">
                <ResponseField name="credentialId" type="string" required>
                  The ID of the passkey credential.
                </ResponseField>

                <ResponseField name="deviceId" type="string" required>
                  The device ID of the passkey credential.
                </ResponseField>

                <ResponseField name="name" type="string">
                  The name of the passkey credential.
                </ResponseField>

                <ResponseField name="aaguid" type="string">
                  The AAGUID of the passkey credential.
                </ResponseField>

                <ResponseField name="credentialBackedUp" type="boolean" required>
                  Whether the passkey credential is backed up.
                </ResponseField>

                <ResponseField name="credentialDeviceType" type="'singleDevice' | 'multiDevice'">
                  The device type of the passkey credential.
                </ResponseField>

                <ResponseField name="parsedUserAgent" type="object">
                  The parsed user agent of the passkey credential.

                  <Expandable title="properties">
                    <ResponseField name="ua" type="string" required>
                      The user agent of the passkey credential.
                    </ResponseField>

                    <ResponseField name="browser" type="object">
                      The browser of the passkey credential.

                      <Expandable title="properties">
                        <ResponseField name="name" type="string">
                          The name of the browser.
                        </ResponseField>

                        <ResponseField name="version" type="string">
                          The version of the browser.
                        </ResponseField>

                        <ResponseField name="major" type="string">
                          The major version of the browser.
                        </ResponseField>
                      </Expandable>
                    </ResponseField>

                    <ResponseField name="device" type="object">
                      The device of the passkey credential.

                      <Expandable title="properties">
                        <ResponseField name="model" type="string">
                          The model of the device.
                        </ResponseField>

                        <ResponseField name="type" type="string">
                          The type of the device.
                        </ResponseField>

                        <ResponseField name="vendor" type="string">
                          The vendor of the device.
                        </ResponseField>
                      </Expandable>
                    </ResponseField>

                    <ResponseField name="engine" type="object">
                      The engine of the passkey credential.

                      <Expandable title="properties">
                        <ResponseField name="name" type="string">
                          The name of the engine.
                        </ResponseField>

                        <ResponseField name="version" type="string">
                          The version of the engine.
                        </ResponseField>
                      </Expandable>
                    </ResponseField>

                    <ResponseField name="os" type="object">
                      The OS of the passkey credential.

                      <Expandable title="properties">
                        <ResponseField name="name" type="string">
                          The name of the OS.
                        </ResponseField>

                        <ResponseField name="version" type="string">
                          The version of the OS.
                        </ResponseField>
                      </Expandable>
                    </ResponseField>

                    <ResponseField name="cpu" type="object">
                      The CPU of the passkey credential.

                      <Expandable title="properties">
                        <ResponseField name="architecture" type="string">
                          The architecture of the CPU.
                        </ResponseField>
                      </Expandable>
                    </ResponseField>
                  </Expandable>
                </ResponseField>

                <ResponseField name="verifiedAt" type="string" required>
                  The date and time the passkey credential was verified in ISO 8601 format.
                </ResponseField>

                <ResponseField name="authenticatorAttachment" type="'platform' | 'cross-platform'">
                  The authenticator attachment of the passkey credential.
                </ResponseField>

                <ResponseField name="aaguidMapping" type="string">
                  The AAGUID mapping of the passkey credential.

                  <Expandable title="properties">
                    <ResponseField name="name" type="string" required>
                      The name of the AAGUID mapping.
                    </ResponseField>

                    <ResponseField name="svgIconLight" type="string">
                      The SVG icon of the AAGUID mapping in light mode.
                    </ResponseField>

                    <ResponseField name="svgIconDark" type="string">
                      The SVG icon of the AAGUID mapping in dark mode.
                    </ResponseField>
                  </Expandable>
                </ResponseField>
              </Expandable>
            </ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="registrationResponse" type="RegistrationResponseJSON">
          The raw WebAuthn registration response returned by the browser. Most apps can ignore this.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="errorCode" type="string">
      See the [Error handling](/sdks/client/web/error-handling) section for more details.
    </ResponseField>

    <ResponseField name="errorDescription" type="string" />
  </Expandable>
</ResponseField>

## Automatic passkey upgrades

<Warning>
  Automatic passkey upgrades are supported in Safari 18+ on macOS, all browsers on iOS 18+, Chrome
  136+ on desktop, and Chrome 142+ on Android.
</Warning>

With passkey upgrades, your app can prompt your user's password manager to automatically create a passkey. This works provided the user
has a password saved for your app in their password manager and has recently authenticated with it.

To enable this, you must set the `useAutoRegister` parameter to `true` when calling `signUp`.

```ts {5} theme={null}
const response = await authsignal.passkey.signUp({
  token: "eyJhbGciOiJ...",
  username: "jane.smith@authsignal.com",
  displayName: "Jane Smith",
  useAutoRegister: true,
});
```

### Example usage

```ts {10} theme={null}
async function onSubmit(formData) {
  // Validate user on BE and return a token from `track`

  // If valid user, attempt to automatically create a passkey
  try {
    const response = await authsignal.passkey.signUp({
      token: "eyJhbGciOiJ...", // Token from `track`
      username: "jane.smith@authsignal.com",
      displayName: "Jane Smith",
      useAutoRegister: true,
    });
  } catch (error) {
    // Failed to automatically create a passkey
    console.error(error);
  }

  // Continue with post-sign-in flow
  window.location.href = "/dashboard";
}
```

## Using a passkey

Calling `signIn` will present the passkey sign-in prompt.
If the user successfully authenticates with their passkey, send the result token to your server to [validate the challenge](/sdks/server/challenges#validate-challenge).

```ts theme={null}
const response = await authsignal.passkey.signIn({ action: "signInWithPasskey" });

if (response.data?.token) {
  // Send the response token to your server to validate the result of the challenge
}
```

<Info>
  Check out our [best practice guide for passkeys on web
  browsers](/authentication-methods/passkey/best-practices-web) for tips on how to implement an
  optimal passkey UX and avoid leading users into dead ends.
</Info>

#### Parameters

<ResponseField name="action" type="string">
  A string which determines how the action associated with the passkey sign-in attempt will be named
  in the [Authsignal Portal](https://portal.authsignal.com). Values are validated with the following
  regex: `^[a-zA-Z0-9_-]{(1, 64)}$`. Cannot be combined with `token`.
</ResponseField>

<ResponseField name="token" type="string">
  A short-lived token obtained by [tracking an
  action](/advanced-usage/authenticator-binding#tracking-an-action-to-generate-a-token). Use this to
  authorize the sign-in attempt instead of `action`. Cannot be combined with `action` or `autofill`.
</ResponseField>

<ResponseField name="autofill" type="boolean">
  Whether to use [passkey autofill](#using-passkey-autofill) (browser conditional UI). Defaults to
  `false`. Cannot be combined with `token` or `preferImmediatelyAvailableCredentials`.
</ResponseField>

<ResponseField name="onVerificationStarted" type="function">
  A callback invoked after the user selects a passkey and the browser ceremony completes, just before
  the result is verified with the Authsignal API. Useful for showing a loading state.
</ResponseField>

<ResponseField name="preferImmediatelyAvailableCredentials" type="boolean">
  Whether to use immediate mediation UI mode, which only prompts the user if a passkey is immediately
  available on the device. Defaults to `false`. See [Immediately available
  credentials](#immediately-available-credentials) for more details.
</ResponseField>

<ResponseField name="useCookies" type="boolean">
  Whether to use cookies to bind the session to the browser. Defaults to `false`. Requires a [custom
  API domain](/advanced-usage/custom-api-domains) on the same parent domain as your site.
</ResponseField>

<ResponseField name="syncCredentials" type="boolean">
  Whether to keep the browser's credential manager in sync with the credentials stored by Authsignal.
  Defaults to `true`. See [Credential syncing](#credential-syncing) for more details.
</ResponseField>

#### Response

<ResponseField name="response" type="AuthsignalResponse<SignInResponse>">
  <Expandable title="properties">
    <ResponseField name="data" type="SignInResponse">
      <Expandable title="properties">
        <ResponseField name="isVerified" type="boolean">
          True if the passkey sign-in attempt was successful.
        </ResponseField>

        <ResponseField name="token" type="string">
          A new token which can be used to [validate the
          challenge](/sdks/server/challenges#validate-challenge) on your server or optionally to
          [bind another authenticator](#authenticator-binding) for the user.
        </ResponseField>

        <ResponseField name="userId" type="string">
          The ID of the Authsignal user if the sign-in attempt was successful.
        </ResponseField>

        <ResponseField name="userAuthenticatorId" type="string">
          The ID of the Authsignal user authenticator if the sign-in attempt was successful.
        </ResponseField>

        <ResponseField name="username" type="string">
          The username associated with the passkey if the sign-in attempt was successful.
        </ResponseField>

        <ResponseField name="displayName" type="string">
          The display name associated with the passkey if the sign-in attempt was successful.
        </ResponseField>

        <ResponseField name="authenticationResponse" type="AuthenticationResponseJSON">
          The raw WebAuthn authentication response returned by the browser. Most apps can ignore this.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="errorCode" type="string">
      See the [Error handling](/sdks/client/web/error-handling) section for more details.
    </ResponseField>

    <ResponseField name="errorDescription" type="string" />
  </Expandable>
</ResponseField>

## Using passkey autofill

This feature requires rendering a text input with the attribute `autocomplete="username webauthn"`:

```html theme={null}
<input type="text" id="username" autocomplete="username webauthn" />
```

It can also be added to a password input:

```html theme={null}
<input type="password" id="password" autocomplete="current-password webauthn" />
```

<Warning>
  The `webauthn` value must be the last value in the `autocomplete` attribute, otherwise the browser
  will not autofill the passkey.
</Warning>

Then when the page loads you should initialize the input for passkey autofill by running the following code.

```ts theme={null}
authsignal.passkey
  .signIn({
    action: "signInWithPasskeyAutofill",
    autofill: true,
  })
  .then((response) => {
    if (response.data?.token) {
      // The user has focused your text input and authenticated with an existing passkey
      // Send the response token to your server to validate the result of the challenge
    }
  });
```

## Immediately available credentials

Setting `preferImmediatelyAvailableCredentials` to `true` uses the browser's immediate mediation UI mode. The passkey prompt is only shown if a credential is immediately available on the device, otherwise the call returns without interrupting the user. This is useful for surfacing a passkey prompt automatically on page load without showing an error to users who don't have a passkey.

```ts theme={null}
const response = await authsignal.passkey.signIn({
  action: "signInWithPasskey",
  preferImmediatelyAvailableCredentials: true,
});

if (response.errorCode === "credential_not_found") {
  // No passkey was immediately available, fall back to another method
} else if (response.data?.token) {
  // Send the response token to your server to validate the result of the challenge
}
```

<Warning>
  Immediate mediation UI mode is only supported in browsers that implement the WebAuthn
  `getClientCapabilities` and immediate `get` APIs. When unsupported, the call returns the
  `immediate_mediation_not_supported` error code. It also cannot be combined with `autofill`.
</Warning>

## Credential syncing

A common source of passkey friction is stale credentials: a passkey is deleted on the server but still appears in the user's browser or password manager, so they select it and hit a dead end.

To prevent this, the SDK uses the [WebAuthn Signal API](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential#static_methods) to keep the browser's credential manager in sync with the passkeys stored by Authsignal. This happens automatically while `syncCredentials` is `true` (the default), so there's no separate method to call:

* After a successful `signUp` or `signIn`, the SDK calls `signalAllAcceptedCredentials` with the full set of the user's valid credentials. The browser removes any passkeys for your app that are no longer on this list.
* When `signIn` fails because the presented passkey is no longer recognized by the server (the `unknown_credential` error code), the SDK calls `signalUnknownCredential` so the browser removes that specific passkey.

Set `syncCredentials` to `false` on `signUp` or `signIn` to opt out.

<Note>
  The Signal API is only available in [supporting
  browsers](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/signalAllAcceptedCredentials_static#browser_compatibility).
  Where it isn't supported, syncing is silently skipped and sign-in is unaffected.
</Note>

## Passkey error handling

When any of the underlying native browser WebAuthn APIs fail, the SDK will throw a `WebAuthnError`.

Most of these errors are benign and can be safely ignored. However, if you wish to handle them, you can do so by catching the error in a `try/catch` block.

Common errors you will encounter during passkey authentication are:

* `ERROR_CEREMONY_ABORTED` - The user cancelled the passkey ceremony. We recommend ignoring this error as it's a normal part of the user experience.
* `ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED` - The user already has a passkey registered for this authenticator (exclusive to `signUp`). For example,
  the user is trying to create an iCloud Keychain passkey but already has one registered in iCloud Keychain.

During development, you may also encounter `ERROR_INVALID_RP_ID` which occurs when the [relying party ID](/authentication-methods/passkey/custom-ui#web) is invalid for the domain.

```ts theme={null}
import { WebAuthnError } from "@authsignal/browser";

try {
  const response = await authsignal.passkey.signUp({
    token: "eyJhbGciOiJ...",
    username: "jane.smith@authsignal.com",
    displayName: "Jane Smith",
  });
} catch (error) {
  if (error instanceof WebAuthnError) {
    if (error.code === "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED") {
      // The user already has a passkey registered for this device
      // You can choose to handle this however you see fit
    }
  }
}
```
