# Configure Passkeys

Set up a passwordless experience using passkeys.

> For the index of this section of the site, see [llms.txt](https://fusionauth.io/docs/llms.txt)

Available since `1.52.0` [Play](https://youtube.com/watch?v=5fnqo_13ZYk)

To use WebAuthn, you have two options. The first option is the FusionAuth hosted login pages interface. FusionAuth's hosted login pages are customizable via themes to make each of the web pages look like your application. The other option is using the WebAuthn API. Both approaches are covered in the sections below.

In either case, you should:

1.  Enable WebAuthn by navigating to Tenant -> Edit -> WebAuthn and toggling the **Enable** option to the active state:
    
    ![Enable WebAuthn for the tenant.](https://fusionauth.io/img/docs/screenshots/passkeys-tenant-enable-webauthn.png)
    
2.  Enable one or more WebAuthn workflows on the tenant configuration:
    
    ![Enable WebAuthn workflows for the tenant.](https://fusionauth.io/img/docs/screenshots/passkeys-tenant-enable-workflows.png)
    

The [WebAuthn Admin Guide](https://fusionauth.io/docs/lifecycle/authenticate-users/passkeys.md) has more information on the tenant configuration options and overriding workflow availability per application.

## Using the FusionAuth Hosted Login Pages

Using the FusionAuth hosted login pages will handle the client-side portion of WebAuthn ceremonies for you. This includes necessary data conversions between the FusionAuth APIs and the [WebAuthn JavaScript API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) as well as the WebAuthn API call itself. There is no additional configuration required for this approach.

FusionAuth supports two WebAuthn workflows: re-authentication and bootstrap authentication.

### Re-authentication

The re-authentication workflow is used to provide a streamlined user experience for repeated logins from the same device. FusionAuth will automatically prompt users to opt in to this workflow when it detects a compatible device. The next time the user is prompted to sign in, they will be redirected to a WebAuthn re-authentication page that lists available passkeys. The user can sign in by clicking a passkey in the list and completing the WebAuthn authentication ceremony.

#### Registration

After a successful login, users will automatically be redirected to prompt them to register or select a WebAuthn passkey for re-authentication if:

1.  The re-authentication workflow is enabled
2.  FusionAuth detects a device suitable for re-authentication using the [isUserVerifyingPlatformAuthenticatorAvailable()](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/isUserVerifyingPlatformAuthenticatorAvailable) JavaScript method

![The re-authentication prompt page asking users to register a passkey.](https://fusionauth.io/img/docs/screenshots/passkeys-reauth-enable.png)

The re-authentication prompt page lists a user's WebAuthn passkeys that meet the attachment and user verification requirements for the tenant configuration. It also has a section to register a new passkey for re-authentication.

There are a few important notes about this page:

*   The listed passkeys may have been registered with an authenticator that is not currently available (e.g. a removable security key)
*   The listed passkeys may have been registered in another browser. Typically passkeys registered with a platform authenticator in one browser will not work in a different browser
*   If the user tries to register a new passkey using the same authenticator as another of their passkeys, the registration ceremony will fail

The user is also presented with the option to skip selecting a re-authentication passkey. Choosing the "Don't ask me again on this device" and then clicking the Not now button will skip the prompt to sign in with a passkey in the future.

The user can select a passkey from the list to complete a WebAuthn authentication ceremony and mark that passkey for re-authentication. Otherwise, they can enter a display name and complete a WebAuthn registration ceremony to generate a new passkey that will be marked for re-authentication. In either case, FusionAuth will redirect to the WebAuthn re-authentication page the next time a login page would be presented.

#### Authentication

When a user visits the hosted login pages and begins an authentication, FusionAuth will check to see if the user has previously set up a passkey for this device. If this can be determined, the user will be redirected to the WebAuthn re-authentication page.

![Re-authentication Login](https://fusionauth.io/img/docs/lifecycle/authenticate-users/passwordless/reauth.png)

WebAuthn passkeys determined to be present will be listed. A user can select which passkey and, by extension, account they would like to use for re-authentication. Clicking a passkey on the list will start the WebAuthn authentication ceremony. Only the selected passkey will be eligible to complete the re-authentication. Once the WebAuthn authentication ceremony has been completed successfully, the user will be authenticated.

The user also has the option to return to the normal login page by clicking the Return to the normal login link.

These pages can be modified by the themes just as any other hosted login page.

### Bootstrap Authentication

The bootstrap workflow requires that the user "bootstrap" the WebAuthn authentication ceremony by providing identifying information such as a username or email address. You could think of this as a "manual" WebAuthn login as opposed to the re-authentication workflow where users are automatically prompted. Users can use any of their passkeys whose authenticator meets the attachment and user verification requirements to complete this authentication process.

#### Registration

If the re-authentication workflow is disabled for the tenant, users need another way to register passkeys for WebAuthn bootstrap authentication because they won't be prompted to add a passkey during authentication. Users can register and manage their own passkeys through [Self Service Account Management](https://fusionauth.io/docs/lifecycle/manage-users/account-management.md).

![The self-service passkeys management page.](https://fusionauth.io/img/docs/screenshots/passkeys-self-service.png)

Users can see a list of passkeys along with a button to delete each one. Below the list of registered passkeys is a button to add a new passkey. Passkey registration on this page will use authenticator attachment and user verification requirements designed to cover the broadest set of use cases. Depending on the tenant configuration, passkeys registered here may not be usable with all enabled WebAuthn workflows. For instance, if passkey ABC is cross-platform, but the tenant is only configured to accept platform authenticators, passkey ABC won't work even though it shows up in this list.

#### Authentication

If the bootstrap workflow is enabled, users will see a button to authenticate with "Fingerprint, device or key" on the initial FusionAuth login page.

![The FusionAuth login page showing the Fingerprint, device or key button.](https://fusionauth.io/img/docs/screenshots/passkeys-bootstrap-button.png)

When a re-authentication passkey has been registered in this browser, users will see a link just above the alternative login methods sections with the text "Return to passkey authentication." This link will take the user back to the re-authentication page.

Clicking the Fingerprint, device or key button will take the user to the WebAuthn bootstrap login page.

![The WebAuthn bootstrap login page.](https://fusionauth.io/img/docs/screenshots/passkeys-bootstrap-login.png)

The user must identify themselves with their username, email address, or phone number, then click the Submit button to begin the WebAuthn authentication ceremony. They can use any of their registered passkeys whose authenticator meets the configured attachment and user verification requirements for the bootstrap workflow.

Once the WebAuthn authentication ceremony has been completed successfully, the user will be authenticated.

These pages can be modified by the themes just as any other hosted login page.

## Using the API Directly

While using the FusionAuth's hosted login pages works for many, you may need more control. You can use the WebAuthn API to register new passkeys and authenticate a user with a passkey. The [WebAuthn API reference docs](https://fusionauth.io/docs/apis/webauthn.md) cover each of the API calls, but this guide will walk you through an implementation.

There are a couple of reasons you might choose this method of integration.

*   You can customize every part of the user login experience
*   You can use a different method to track re-authentication passkey availability

When using this option, you must set up an [API key](https://fusionauth.io/docs/apis/authentication.md) with the appropriate permissions. The minimum level of privilege required is the `POST` permission to the `/api/webauthn/start` and `/api/webauthn/register/start` endpoints.

The WebAuthn registration and authentication ceremonies both have a similar set of steps.

1.  Start the WebAuthn ceremony via an API call to FusionAuth
2.  Transform the API response containing the options for the ceremony
3.  Invoke the [WebAuthn JavaScript API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) in the browser using the transformed options
4.  Transform the authenticator response to prevent issues during network transport
5.  Include the transformed response in an API call to FusionAuth to complete the ceremony

The registration and authentication ceremonies are covered in their own sections below.

### Registration

Use the WebAuthn registration ceremony to create a new passkey for a user.

#### Starting the Registration

You start a WebAuthn registration by calling the [/api/webauthn/register/start](https://fusionauth.io/docs/apis/webauthn/start-a-webauthn-passkey-registration.md) endpoint.

Start WebAuthn Registration API call

```shell
API_KEY=...
REQUEST_PAYLOAD='{...}'
curl  -H "Content-type: application/json" -H "Authorization: $API_KEY" https://local.fusionauth.io/api/webauthn/register/start -d $REQUEST_PAYLOAD
```

Here's an example request payload:

Start WebAuthn Registration Request JSON

```json
{
  "displayName": "Chrome Touch ID",
  "userId": "703fe2d2-2d39-4cb7-b76d-0b9918ed2457",
  "workflow": "reauthentication"
}
```

The call to [/api/webauthn/register/start](https://fusionauth.io/docs/apis/webauthn/start-a-webauthn-passkey-registration.md) begins the registration ceremony and returns a response with options for the [WebAuthn JavaScript API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API), including a one-time challenge specific to this ceremony:

Start WebAuthn Registration Response JSON

```json
{
  "options": {
    "attestation": "none",
    "authenticatorSelection": {
      "authenticatorAttachment": "platform",
      "userVerification": "required"
    },
    "challenge": "fdnW2u1_Nk9_FY2SprU4mPs0NgBTbo9tOO5Q9EvO1Oc",
    "excludeCredentials": [
      {
        "id": "MBU0eBsaNAt4VEh4Lbn2sQ==",
        "transports": [
          "internal"
        ],
        "type": "public-key"
      }
    ],
    "extensions": {
      "credProps": true
    },
    "pubKeyCredParams": [
      {
        "alg": -36,
        "type": "public-key"
      },
      {
        "alg": -35,
        "type": "public-key"
      },
      {
        "alg": -7,
        "type": "public-key"
      },
      {
        "alg": -39,
        "type": "public-key"
      },
      {
        "alg": -38,
        "type": "public-key"
      },
      {
        "alg": -37,
        "type": "public-key"
      },
      {
        "alg": -259,
        "type": "public-key"
      },
      {
        "alg": -258,
        "type": "public-key"
      },
      {
        "alg": -257,
        "type": "public-key"
      }
    ],
    "rp": {
      "id": "piedpiper.com",
      "name": "Pied Piper"
    },
    "timeout": 180000,
    "user": {
      "displayName": "Chrome Touch ID",
      "id": "NzAzZmUyZDItMmQzOS00Y2I3LWI3NmQtMGI5OTE4ZWQyNDU3",
      "name": "richard@fusionauth.io"
    }
  }
}
```

#### Transform the Options Response

Certain fields on the [/api/webauthn/register/start](https://fusionauth.io/docs/apis/webauthn/start-a-webauthn-passkey-registration.md) response are returned as base64url-encoded strings. These have to be translated to JavaScript [ArrayBuffers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)s before being passed to the [WebAuthn JavaScript API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API). The response should be left as-is other than the transformations for these particular fields.

The following response fields need to be converted to `ArrayBuffer`s if they are present:

*   **options.challenge**
*   **options.excludeCredentials\[x\].id**
*   **options.user.id**

See the [/api/webauthn/register/start](https://fusionauth.io/docs/apis/webauthn/start-a-webauthn-passkey-registration.md) response body section for more details.

You can use the following JavaScript function to perform the conversion:

Function to convert a base64url-encoded string to an ArrayBuffer

```javascript
function base64URLToBuffer(base64URL) {
  const base64 = base64URL.replace(/-/g, '+').replace(/_/g, '/');
  const padLen = (4 - (base64.length % 4)) % 4;
  return Uint8Array.from(atob(base64.padEnd(base64.length + padLen, '=')), c => c.charCodeAt(0));
}
```

The following is an ES6 JavaScript snippet to convert the API response to the object that is passed to the [WebAuthn JavaScript API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API). In the following example `options` is the value of the **options** field on the [/api/webauthn/register/start](https://fusionauth.io/docs/apis/webauthn/start-a-webauthn-passkey-registration.md) API response, and `creationOptions` is the object that is passed to the WebAuthn JavaScript API's `navigator.credentials.create()` function.

Transform API response for WebAuthn JavaScript API

```javascript
const creationOptions = {
  // publicKey indicates this is for WebAuthn
  publicKey: {
    ...options,
    challenge: base64URLToBuffer(options.challenge),
    excludeCredentials: options.excludeCredentials?.map(c => {
      return {
        ...c,
        id: base64URLToBuffer(c.id)
      }
    }) ?? [],
    user: {
      ...options.user,
      id: base64URLToBuffer(options.user.id)
    }
  }
};
```

#### Invoke the WebAuthn JavaScript API

Invoke [navigator.credentials.create()](https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create) on the WebAuthn JavaScript API, passing the transformed FusionAuth API response (`creationOptions` in the snippet above) as a parameter.

The function returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that resolves with a [PublicKeyCredential](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential). Reference MDN documentation or the WebAuthn [specification](https://www.w3.org/TR/webauthn-2/) for details.

#### Transform the Authenticator Response

Certain fields on the [PublicKeyCredential](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential) response from the [navigator.credentials.create()](https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create) WebAuthn JavaScript API call need to be transformed from [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)s to base64url-encoded strings for transport over the network.

The following response fields need to be converted from `ArrayBuffer`s to base64url-encoded strings if they are present where **credential** refers to the `PublicKeyCredential` returned by the `navigator.credentials.create()` call:

*   **credential.response.attestationObject**
*   **credential.response.clientDataJSON**

See the [/api/webauthn/register/complete](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-registration.md) request body section for more details.

You can use the following JavaScript function to perform the conversion:

Function to convert an ArrayBuffer to a base64url-encoded string

```javascript
function bufferToBase64URL(buffer) {
  const bytes = new Uint8Array(buffer);
  let string = '';
  bytes.forEach(b => string += String.fromCharCode(b));

  const base64 = btoa(string);
  return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
```

You should also use convenience methods on the `PublicKeyCredential` and its **response** field to extract information on transports supported by the authenticator and any requested client extension results. The **response** field contains an [AuthenticatorAttestationResponse](https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAttestationResponse) object after a successful call to `navigator.credentials.create()`. The following is an ES6 JavaScript snippet to convert the `PublicKeyCredential` to a suitable format for the FusionAuth [/api/webauthn/register/complete](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-registration.md) API call. In the following example, `credential` is the `PublicKeyCredential` object returned by the WebAuthn JavaScript API's `navigator.credentials.create()` function, and `transformedCredential` is the object that will be included in the [credential](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-registration.md) on the FusionAuth [/api/webauthn/register/complete](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-registration.md) API request.

Transform WebAuthn JavaScript API response for FusionAuth API request

```javascript
const transformedCredential = {
  id: credential.id,
  response: {
    attestationObject: bufferToBase64URL(credential.response.attestationObject),
    clientDataJSON: bufferToBase64URL(credential.response.clientDataJSON)
  },
  type: credential.type,
  clientExtensionResults: credential.getClientExtensionResults(),
  transports: typeof credential.response.getTransports === 'undefined' ? [] : credential.response.getTransports()
};
```

#### Completing the Registration

You complete a WebAuthn registration by calling the [/api/webauthn/register/complete](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-registration.md) endpoint.

Complete WebAuthn Registration API call

```shell
REQUEST_PAYLOAD='{...}'
curl  -H "Content-type: application/json" https://local.fusionauth.io/api/webauthn/register/complete -d $REQUEST_PAYLOAD
```

Here's an example request payload where **credential** is the `transformedCredential` from the snippet above:

Complete WebAuthn Registration Request JSON

```json
{
  "credential": {
    "clientExtensionResults": {
      "credProps": {
        "rk": true
      }
    },
    "id": "HdN9wqP9mqOonacmiM2gIjASFYg",
    "response": {
      "attestationObject": "v2dhdHRTdG109mhhdXRoRGF0YViemPsv4VW-wth0V9gzsxWRFn-1SyC9gM4-QC4ptMFDNhVFAAAAFmhGAxjmXUwbl-q5EoJkWH0AELmJieDlA5kNXEtcIgWnp1a_YTMmYTECYi0xAWItMlggTmZZ1aeY0PP8-Ebdufqcg9TWEs_mJaFJmJ57uRyQ1BxiLTNYIQCzqsN1dkCeZKpWGKEVP0-eqBlkKGNCjfcdg5221SRZX_9jZm10ZG5vbmX_",
      "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJ2dHk0WG1ES01EMmxTc2dVOEpsOFlIVEtBcGZlWXpMdkRVUksxTXpGd1JvIiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvcmlnaW4iOiJodHRwczovL2Z1c2lvbmF1dGguaW8iLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0="
    },
    "transports": [
      "internal"
    ],
    "type": "public-key"
  },
  "origin": "https://auth.piedpiper.com",
  "rpId": "piedpiper.com",
  "userId": "703fe2d2-2d39-4cb7-b76d-0b9918ed2457"
}
```

The call to [/api/webauthn/register/complete](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-registration.md) completes the registration ceremony by validating the request against the WebAuthn [specification](https://www.w3.org/TR/webauthn-2/), extracting the public key, and associating the new passkey with the user. This API returns the new WebAuthn passkey registered for the user.

Complete WebAuthn Registration Response JSON

```json
{
  "credential": {
    "algorithm": -7,
    "attestationType": "none",
    "authenticatorSupportsUserVerification": true,
    "credentialId": "HdN9wqP9mqOonacmiM2gIjASFYg",
    "data": {},
    "displayName": "Chrome Touch ID",
    "name": "richard@fusionauth.io",
    "id": "c664318a-2384-4c35-9475-9a200e1d3b72",
    "insertInstant": 1668011701792,
    "discoverable": false,
    "lastUseInstant": 1668021630599,
    "publicKey": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEz9DI9AQfZn1aDJG5sw3Ckl7SoQ7E\nLPElDoJMijphvVigTcNMTc8H9Xptl8B20QHMOXGTzaUxLGNY1c8yhw9VVA==\n-----END PUBLIC KEY-----",
    "relyingPartyId": "piedpiper.com",
    "signCount": 41,
    "tenantId": "30663132-6464-6665-3032-326466613934",
    "transports": [
      "internal"
    ],
    "userId": "703fe2d2-2d39-4cb7-b76d-0b9918ed2457"
  }
}
```

### Authentication

Use the WebAuthn authentication ceremony to authenticate a user with a passkey. There is also a separate FusionAuth API to [complete the assertion](#completing-the-assertion), which will validate the authenticator response without logging the user in.

#### Starting the Authentication

You start a WebAuthn authentication ceremony by calling the [/api/webauthn/start](https://fusionauth.io/docs/apis/webauthn/start-a-webauthn-passkey-assertion-or-authentication.md) endpoint.

Start WebAuthn Authentication API call

```shell
API_KEY=...
REQUEST_PAYLOAD='{...}'
curl  -H "Content-type: application/json" -H "Authorization: $API_KEY" https://local.fusionauth.io/api/webauthn/start -d $REQUEST_PAYLOAD
```

Here's an example request payload:

Start WebAuthn Authentication Request JSON

```json
{
  "applicationId": "82b24314-95f6-4393-b3da-72c341185244",
  "userId": "703fe2d2-2d39-4cb7-b76d-0b9918ed2457",
  "workflow": "reauthentication"
}
```

The call to [/api/webauthn/start](https://fusionauth.io/docs/apis/webauthn/start-a-webauthn-passkey-assertion-or-authentication.md) begins the authentication ceremony and returns a response with options for the [WebAuthn JavaScript API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API), including a one-time challenge specific to this ceremony:

Start WebAuthn Authentication Response JSON

```json
{
  "options": {
    "allowCredentials": [
      {
        "id": "HdN9wqP9mqOonacmiM2gIjASFYg",
        "transports": [
          "internal"
        ],
        "type": "public-key"
      }
    ],
    "challenge": "fdnW2u1_Nk9_FY2SprU4mPs0NgBTbo9tOO5Q9EvO1Oc",
    "rpId": "piedpiper.com",
    "timeout": 180000,
    "userVerification": "required"
  }
}
```

#### Transform the Options Response

Certain fields on the [/api/webauthn/start](https://fusionauth.io/docs/apis/webauthn/start-a-webauthn-passkey-assertion-or-authentication.md) response are returned as base64url-encoded strings. These have to be translated to JavaScript [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)s before being passed to the [WebAuthn JavaScript API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API). The response should be left as-is other than the transformations for these particular fields.

The following response fields need to be converted to `ArrayBuffer`s if they are present:

*   **options.challenge**
*   **options.allowCredentials\[x\].id**

See the [/api/webauthn/start](https://fusionauth.io/docs/apis/webauthn/start-a-webauthn-passkey-assertion-or-authentication.md) response body section for more details.

You can use the following JavaScript function to perform the conversion:

Function to convert a base64url-encoded string to an \`ArrayBuffer\`

```javascript
function base64URLToBuffer(base64URL) {
  const base64 = base64URL.replace(/-/g, '+').replace(/_/g, '/');
  const padLen = (4 - (base64.length % 4)) % 4;
  return Uint8Array.from(atob(base64.padEnd(base64.length + padLen, '=')), c => c.charCodeAt(0));
}
```

The following is an ES6 JavaScript snippet to convert the API response to the object that is passed to the [WebAuthn JavaScript API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API). In the following example `options` is the value of the **options** field on the [/api/webauthn/start](https://fusionauth.io/docs/apis/webauthn/start-a-webauthn-passkey-assertion-or-authentication.md) API response, and `requestOptions` is the object that is passed to the WebAuthn JavaScript API's `navigator.credentials.get()` function.

Transform API response for WebAuthn JavaScript API

```javascript
const requestOptions = {
  // publicKey indicates this is for WebAuthn
  publicKey: {
    ...options,
    challenge: base64URLToBuffer(options.challenge),
    allowCredentials: options.allowCredentials.map(c => {
      return {
        ...c,
        id: base64URLToBuffer(c.id)
      }
    })
  }
};
```

#### Invoke the WebAuthn JavaScript API

Invoke [navigator.credentials.get()](https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get) on the WebAuthn JavaScript API, passing the transformed FusionAuth API response (`requestOptions` in the snippet above) as a parameter.

The function returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that resolves with a [PublicKeyCredential](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential). Reference MDN documentation or the WebAuthn [specification](https://www.w3.org/TR/webauthn-2/) for details.

#### Transform the Authenticator Response

Certain fields on the [PublicKeyCredential](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential) response from the [navigator.credentials.get()](https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get) WebAuthn JavaScript API call need to be transformed from [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)s to base64url-encoded strings for transport over the network.

The following response fields need to be converted from `ArrayBuffer`s to base64url-encoded strings if they are present where **credential** refers to the `PublicKeyCredential` returned by the `navigator.credentials.get()` call:

*   **credential.response.authenticatorData**
*   **credential.response.clientDataJSON**
*   **credential.response.signature**
*   **credential.response.userHandle**

See the [/api/webauthn/login](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-authentication.md) or [/api/webauthn/assert](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-assertion.md) request body sections for more details.

You can use the following JavaScript function to perform the conversion:

Function to convert an ArrayBuffer to a base64url-encoded string

```javascript
function bufferToBase64URL(buffer) {
  const bytes = new Uint8Array(buffer);
  let string = '';
  bytes.forEach(b => string += String.fromCharCode(b));

  const base64 = btoa(string);
  return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
```

You should also use convenience methods on the `PublicKeyCredential` to extract information on any requested client extension results. The **response** field contains an [AuthenticatorAssertionResponse](https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAssertionResponse) object after a successful call to `navigator.credentials.get()`. The following is an ES6 JavaScript snippet to convert the `PublicKeyCredential` to a suitable format for the FusionAuth [/api/webauthn/login](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-authentication.md) or [/api/webauthn/assert](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-assertion.md) API call. In the following example, `credential` is the `PublicKeyCredential` object returned by the WebAuthn JavaScript API's `navigator.credentials.get()` function, and `transformedCredential` is the object that will be included in the [credential](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-assertion.md) field on the FusionAuth API request.

Transform WebAuthn JavaScript API response for FusionAuth API request

```javascript
let userHandle = undefined;
if (credential.response.userHandle) {
  userHandle = bufferToBase64URL(credential.response.userHandle);
}
const transformedCredential = {
  id: credential.id,
  response: {
    authenticatorData: bufferToBase64URL(credential.response.authenticatorData),
    clientDataJSON: bufferToBase64URL(credential.response.clientDataJSON),
    signature: bufferToBase64URL(credential.response.signature),
    userHandle
  },
  type: credential.type,
  clientExtensionResults: credential.getClientExtensionResults()
};
```

#### Completing the Authentication

You complete a WebAuthn authentication by calling the [/api/webauthn/login](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-authentication.md) endpoint.

Complete WebAuthn Authentication API call

```shell
REQUEST_PAYLOAD='{...}'
curl  -H "Content-type: application/json" https://local.fusionauth.io/api/webauthn/login -d $REQUEST_PAYLOAD
```

Here's an example request payload where **credential** is the `transformedCredential` from the snippet above:

Complete WebAuthn Authentication Request JSON

```json
{
  "credential": {
    "id": "HdN9wqP9mqOonacmiM2gIjASFYg",
    "response": {
      "authenticatorData": "mPsv4VW-wth0V9gzsxWRFn-1SyC9gM4-QC4ptMFDNhUFAAAAKQ==",
      "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJTeTYzQldla285blFsYzVOQmVWcE1BWjFtQ0hvdzBXUXVFeDRJU2xuQzR3IiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvcmlnaW4iOiJodHRwczovL2Z1c2lvbmF1dGguaW8iLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0=",
      "signature": "MEUCIQDUwlDp9aNcYQmvpVGLJ35H8lRWjWsfjVsfPf_5HhY3GwIgahBhISBxv82ZS4ll8TZiJp5bIN2CGx_-VjUymfzKqGA=",
      "userHandle": "NzAzZmUyZDItMmQzOS00Y2I3LWI3NmQtMGI5OTE4ZWQyNDU3"
    },
    "type": "public-key"
  },
  "origin": "https://auth.piedpiper.com",
  "rpId": "piedpiper.com"
}
```

The call to [/api/webauthn/login](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-authentication.md) completes the authentication ceremony by validating the request against the WebAuthn [specification](https://www.w3.org/TR/webauthn-2/), verifying the signature, and authenticating the user. This API returns the user object for the authenticated user along with access tokens for the successful authentication.

Complete WebAuthn Authentication Response JSON

```json
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0ODUxNDA5ODQsImlhdCI6MTQ4NTEzNzM4NCwiaXNzIjoiYWNtZS5jb20iLCJzdWIiOiIyOWFjMGMxOC0wYjRhLTQyY2YtODJmYy0wM2Q1NzAzMThhMWQiLCJhcHBsaWNhdGlvbklkIjoiNzkxMDM3MzQtOTdhYi00ZDFhLWFmMzctZTAwNmQwNWQyOTUyIiwicm9sZXMiOltdfQ.Mp0Pcwsz5VECK11Kf2ZZNF_SMKu5CgBeLN9ZOP04kZo",
  "user": {
    "active": true,
    "birthDate": "1976-05-30",
    "breachedPasswordLastCheckedInstant": 1471786483322,
    "breachedPasswordStatus": "None",
    "connectorId": "e3306678-a53a-4964-9040-1c96f36dda72",
    "data": {
      "displayName": "Johnny Boy",
      "favoriteColors": [
        "Red",
        "Blue"
      ]
    },
    "email": "example@fusionauth.io",
    "expiry": 1571786483322,
    "firstName": "John",
    "fullName": "John Doe",
    "id": "00000000-0000-0001-0000-000000000000",
    "identities": [
      {
        "insertInstant": 1742936980069,
        "lastLoginInstant": 1742936980069,
        "lastUpdateInstant": 1742936980069,
        "primary": true,
        "type": "email",
        "value": "example@fusionauth.io",
        "verified": false,
        "verifiedReason": "Disabled"
      },
      {
        "insertInstant": 1742936980069,
        "lastLoginInstant": 1742936980069,
        "lastUpdateInstant": 1742936980069,
        "primary": true,
        "type": "phoneNumber",
        "value": "+13035551212",
        "verified": false,
        "verifiedReason": "Disabled"
      },
      {
        "displayValue": "johnny123",
        "insertInstant": 1742936980069,
        "lastLoginInstant": 1742936980069,
        "lastUpdateInstant": 1742936980069,
        "moderationStatus": "ACTIVE",
        "primary": true,
        "type": "username",
        "value": "johnny123",
        "verified": false,
        "verifiedReason": "Unverifiable"
      }
    ],
    "imageUrl": "http://65.media.tumblr.com/tumblr_l7dbl0MHbU1qz50x3o1_500.png",
    "insertInstant": 1742936980069,
    "lastLoginInstant": 1742936980069,
    "lastName": "Doe",
    "middleName": "William",
    "mobilePhone": "303-555-1234",
    "passwordChangeRequired": false,
    "passwordLastUpdateInstant": 1742936980069,
    "phoneNumber": "+13035551212",
    "preferredLanguages": [
      "en",
      "fr"
    ],
    "registrations": [
      {
        "applicationId": "10000000-0000-0002-0000-000000000001",
        "data": {
          "displayName": "Johnny",
          "favoriteSports": [
            "Football",
            "Basketball"
          ]
        },
        "id": "00000000-0000-0002-0000-000000000000",
        "insertInstant": 1742936980069,
        "lastLoginInstant": 1742936980069,
        "preferredLanguages": [
          "en",
          "fr"
        ],
        "roles": [
          "user",
          "community_helper"
        ],
        "username": "johnny123",
        "usernameStatus": "ACTIVE",
        "verified": true,
        "verifiedInstant": 1742936980069
      }
    ],
    "timezone": "America/Denver",
    "tenantId": "f24aca2b-ce4a-4dad-951a-c9d690e71415",
    "twoFactor": {
      "methods": [
        {
          "authenticator": {
            "algorithm": "HmacSHA1",
            "codeLength": 6,
            "timeStep": 30
          },
          "id": "35VW",
          "method": "authenticator"
        },
        {
          "id": "V7SH",
          "method": "sms",
          "mobilePhone": "555-555-5555"
        },
        {
          "email": "example@fusionauth.io",
          "id": "7K2G",
          "method": "email"
        }
      ]
    },
    "usernameStatus": "ACTIVE",
    "username": "johnny123",
    "verified": true,
    "verifiedInstant": 1742936980069
  }
}
```

#### Completing the Assertion

You can also complete a WebAuthn authentication ceremony by calling the [/api/webauthn/assert](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-assertion.md) endpoint. This endpoint performs the same request validation and signature verification as [/api/webauthn/login](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-authentication.md), but it does not authenticate the user. It may be useful to validate that a user has access to passkey without authenticating them.

Complete WebAuthn Assertion API call

```shell
REQUEST_PAYLOAD='{...}'
curl  -H "Content-type: application/json" https://local.fusionauth.io/api/webauthn/assert -d $REQUEST_PAYLOAD
```

Here's an example request payload where **credential** is the `transformedCredential` from the snippet above:

Complete WebAuthn Assertion Request JSON

```json
{
  "credential": {
    "id": "HdN9wqP9mqOonacmiM2gIjASFYg",
    "response": {
      "authenticatorData": "mPsv4VW-wth0V9gzsxWRFn-1SyC9gM4-QC4ptMFDNhUFAAAAKQ==",
      "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJTeTYzQldla285blFsYzVOQmVWcE1BWjFtQ0hvdzBXUXVFeDRJU2xuQzR3IiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvcmlnaW4iOiJodHRwczovL2Z1c2lvbmF1dGguaW8iLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0=",
      "signature": "MEUCIQDUwlDp9aNcYQmvpVGLJ35H8lRWjWsfjVsfPf_5HhY3GwIgahBhISBxv82ZS4ll8TZiJp5bIN2CGx_-VjUymfzKqGA=",
      "userHandle": "NzAzZmUyZDItMmQzOS00Y2I3LWI3NmQtMGI5OTE4ZWQyNDU3"
    },
    "type": "public-key"
  },
  "origin": "https://auth.piedpiper.com",
  "rpId": "piedpiper.com"
}
```

The call to [/api/webauthn/assert](https://fusionauth.io/docs/apis/webauthn/complete-a-webauthn-passkey-assertion.md) completes the authentication ceremony by validating the request against the WebAuthn [specification](https://www.w3.org/TR/webauthn-2/) and verifying the signature, but it does *not* authenticate the user. This API returns the WebAuthn passkey that was used to complete the assertion.

Complete WebAuthn Assertion Response JSON

```json
{
  "credential": {
    "algorithm": -7,
    "attestationType": "none",
    "authenticatorSupportsUserVerification": true,
    "credentialId": "HdN9wqP9mqOonacmiM2gIjASFYg",
    "data": {},
    "displayName": "Chrome Touch ID",
    "name": "richard@fusionauth.io",
    "id": "c664318a-2384-4c35-9475-9a200e1d3b72",
    "insertInstant": 1668011701792,
    "discoverable": false,
    "lastUseInstant": 1668021630599,
    "publicKey": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEz9DI9AQfZn1aDJG5sw3Ckl7SoQ7E\nLPElDoJMijphvVigTcNMTc8H9Xptl8B20QHMOXGTzaUxLGNY1c8yhw9VVA==\n-----END PUBLIC KEY-----",
    "relyingPartyId": "piedpiper.com",
    "signCount": 41,
    "tenantId": "30663132-6464-6665-3032-326466613934",
    "transports": [
      "internal"
    ],
    "userId": "703fe2d2-2d39-4cb7-b76d-0b9918ed2457"
  }
}
```
---

## Other pages in Passkeys

> For the full index of this section, see [Lifecycle](https://fusionauth.io/docs/llms-lifecycle.txt).

- [Customize Passkeys](https://fusionauth.io/docs/lifecycle/authenticate-users/passkeys/customize.md): Customize the passkey experience in your application.
- [Passkeys](https://fusionauth.io/docs/lifecycle/authenticate-users/passkeys.md): An overview of the passwordless capabilities of FusionAuth.
