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

# Integrate WebAuthn via the API

> Register and authenticate WebAuthn credentials with the Consumer API.

WebAuthn has two phases: registration and authentication. Registration attaches a WebAuthn device to a user; authentication verifies a login attempt using that device.

This guide describes how to implement both WebAuthn registration and authentication when using a backend Stytch integration. It also provides simplified example code for both the backend and frontend pieces of a WebAuthn integration.

We also recommend checking out our backend WebAuthn example app to accompany this guide:

* [Hosted demo](https://www.stytchdemo.com/recipes/api-webauthn)
* [WebAuthn registration code](https://github.com/stytchauth/stytch-nextjs-integration/blob/7d2f2b8575d300900419cd4808305788db38b5f3/components/EmailWebAuthn/WebAuthnRegister.tsx)
* [WebAuthn authentication code](https://github.com/stytchauth/stytch-nextjs-integration/blob/7d2f2b8575d300900419cd4808305788db38b5f3/components/EmailWebAuthn/WebAuthnAuthenticateButton.tsx)

<Steps>
  <Step title="Create a Stytch User">
    If the user attempting to register isn't yet associated with a Stytch `user_id`, you'll have to create a new Stytch User via the [Create User endpoint](/api-reference/consumer/api/users/create-user). The resulting `user_id` will be used to register a new WebAuthn device.

    ```js theme={null}
    const stytch = require("stytch");

    const client = new stytch.Client({
        project_id: "${projectId}",
        secret: "${secret}",
      }
    );

    const params = {
        email: "${user_email}",
    };
    client.users.create(params)
      .then(resp => {
          console.log(resp)
      })
      .catch(err => {
          console.log(err)
      });
    ```
  </Step>

  <Step title="Register a WebAuthn device">
    To authenticate with WebAuthn, you first need to register a WebAuthn device.

    First, you'll make a request to the [Start WebAuthn registration](/api-reference/consumer/api/passkeys-webauthn/register/start) endpoint. You need two fields for the request: a Stytch `user_id` and your login page's domain. When using built-in browser methods like `navigator.credentials.create()` and `navigator.credentials.get()`, as we will in this guide, you'll also need to set the `use_base64_url_encoding` option to `true`.

    Next, you'll create a public key using the `public_key_credential_creation_options` from the Start WebAuthn registration response and use that to call the browser's built-in `navigator.credentials.create()` method.

    If the `navigator.credentials.create()` call is successful, pass the resulting public key credential into our [Register WebAuthn](/api-reference/consumer/api/passkeys-webauthn/register/register) endpoint.

    Here is simplified backend and frontend example code demonstrating the full WebAuthn registration flow:

    ```js theme={null}
    // Backend code

    export async function callWebauthnRegisterStart() {
      const response = await stytchClient.webauthn.registerStart({
        user_id: "${userId}",
        domain: "${exampleDomain}",
        use_base64_url_encoding: true,
      });

      return response.json();
    }

    export async function callWebauthnRegister(public_key_credential) {
      const response = await stytchClient.webauthn.register({
        user_id: "${userId}",
        public_key_credential: public_key_credential,
      });

      return response.json();
    }
    ```

    ```js theme={null}
    // Frontend code

    const webAuthnRegisterStartResponse = await callWebauthnRegisterStart();
    const options = webAuthnRegisterStartResponse.public_key_credential_creation_options;

    const publicKey = PublicKeyCredential.parseCreationOptionsFromJSON(JSON.parse(options));
    const credential = (await navigator.credentials.create({publicKey})) as PublicKeyCredential;

    await callWebauthnRegister(public_key_credential: JSON.stringify(credential.toJSON()))
      .then(resp => { /* WebAuthn authenticator successfully registered */ })
      .catch(err => { /* Registration error */ });
    ```
  </Step>

  <Step title="Authenticate WebAuthn">
    Now that user has an active WebAuthn registration, you can use it for authentication.

    First, you'll make a request to the [Start WebAuthn authentication](/api-reference/consumer/api/passkeys-webauthn/authenticate/start) endpoint. Similarly to in **Step 2**, when using built-in browser methods, you'll need to set the `use_base64_url_encoding` option to `true`.

    Next, you'll create a public key using the `public_key_credential_request_options` from the Start WebAuthn authentication response and use that to call the browser's built-in `navigator.credentials.get()` method.

    If the `navigator.credentials.get()` call is successful, pass the resulting public key credential into our [Authenticate WebAuthn](/api-reference/consumer/api/passkeys-webauthn/authenticate/authenticate) endpoint. If the Authenticate WebAuthn call succeeds, your user has successfully authenticated.

    Here is simplified backend and frontend example code demonstrating the full WebAuthn authentication flow:

    ```js theme={null}
    // Backend code

    export async function callWebauthnAuthenticateStart() {
      const response = await stytchClient.webauthn.authenticateStart({
        domain: "${exampleDomain}",
        use_base64_url_encoding: true,
      });

      return response.json();
    }

    export async function callWebauthnAuthenticate(public_key_credential) {
      const response = await stytchClient.webauthn.authenticate({
        public_key_credential: public_key_credential,
        session_duration_minutes: 60,
      })

      return response.json();
    }
    ```

    ```js theme={null}
    // Frontend code

    const webAuthnAuthenticateStartResponse = await callWebauthnAuthenticateStart();
    const options = webAuthnAuthenticateStartResponse.public_key_credential_request_options;

    const publicKey = PublicKeyCredential.parseRequestOptionsFromJSON(JSON.parse(options));
    const credential = (await navigator.credentials.get({publicKey})) as PublicKeyCredential;

    await callWebauthnAuthenticate(JSON.stringify(credential.toJSON()))
      .then(resp => { /* User has successfully authenticated */ })
      .catch(err => { /* Authentication error */ });
    ```
  </Step>

  <Step title="[Optional but recommended] Manually serialize the public key credentials">
    In the above registration and authentication steps, we serialized the public key credentials by simply calling `credential.toJSON()`. This works in most cases, but there are some known incompatibilities with certain password managers and the public key credential's `toJSON()` method.

    To avoid these incompatibilities, you can manually serialize the public key credentials instead of calling `credential.toJSON()`. Public key credential serialization code will look something like this, where the `SerializedAttestationCredential` is used in the WebAuthn registration request and the `SerializedAssertionCredential` is used in the WebAuthn authentication request:

    ```js theme={null}
    export interface SerializedAttestationCredential {
      id: string;
      rawId: string;
      type: string;
      response: {
        clientDataJSON: string;
        attestationObject: string;
      };
      authenticatorAttachment?: "platform" | "cross-platform";
    }

    export function serializeAttestationCredential(
      credential: PublicKeyCredential,
    ): SerializedAttestationCredential {
      const response = credential.response as AuthenticatorAttestationResponse;

      return {
        id: credential.id,
        rawId: base64urlEncode(credential.rawId),
        type: credential.type,
        response: {
          clientDataJSON: base64urlEncode(response.clientDataJSON),
          attestationObject: base64urlEncode(response.attestationObject),
        },
        authenticatorAttachment:
          (credential as any).authenticatorAttachment ?? undefined,
      };
    }

    export interface SerializedAssertionCredential {
      id: string;
      rawId: string;
      type: string;
      response: {
        clientDataJSON: string;
        authenticatorData: string;
        signature: string;
        userHandle: string | null;
      };
      authenticatorAttachment?: "platform" | "cross-platform";
    }

    export function serializeAssertionCredential(
      credential: PublicKeyCredential,
    ): SerializedAssertionCredential {
      const response = credential.response as AuthenticatorAssertionResponse;

      return {
        id: credential.id,
        rawId: base64urlEncode(credential.rawId),
        type: credential.type,
        response: {
          clientDataJSON: base64urlEncode(response.clientDataJSON),
          authenticatorData: base64urlEncode(response.authenticatorData),
          signature: base64urlEncode(response.signature),
          userHandle: response.userHandle
            ? base64urlEncode(response.userHandle)
            : null,
        },
        authenticatorAttachment:
          (credential as any).authenticatorAttachment ?? undefined,
      };
    }

    function base64urlEncode(buffer: ArrayBuffer): string {
      const bytes = new Uint8Array(buffer);
      let binary = "";

      for (let i = 0; i < bytes.length; i++) {
        binary += String.fromCharCode(bytes[i]);
      }

      return btoa(binary)
        .replace(/\+/g, "-")
        .replace(/\//g, "_")
        .replace(/=+$/g, "");
    }
    ```

    Here's updated frontend registration and authentication code that uses the above manual serialization methods instead of `toJSON()`:

    ```js theme={null}
    // Registration

    const webAuthnRegisterStartResponse = await callWebauthnRegisterStart();
    const options = webAuthnRegisterStartResponse.public_key_credential_creation_options;

    const publicKey = PublicKeyCredential.parseCreationOptionsFromJSON(JSON.parse(options));
    const credential = (await navigator.credentials.create({publicKey})) as PublicKeyCredential;

    const serializedCredential = serializeAttestationCredential(
      credential as PublicKeyCredential,
    );

    await callWebauthnRegister(JSON.stringify(serializedCredential))
      .then(resp => { /* WebAuthn authenticator successfully registered */ })
      .catch(err => { /* Registration error */ });
    ```

    ```js theme={null}
    // Authentication

    const webAuthnAuthenticateStartResponse = await callWebauthnAuthenticateStart();
    const options = webAuthnAuthenticateStartResponse.public_key_credential_request_options;

    const publicKey = PublicKeyCredential.parseRequestOptionsFromJSON(JSON.parse(options));
    const credential = (await navigator.credentials.get({publicKey})) as PublicKeyCredential;

    const serializedCredential = serializeAssertionCredential(
      credential as PublicKeyCredential,
    );

    await callWebauthnAuthenticate(JSON.stringify(serializedCredential))
      .then(resp => { /* User has successfully authenticated */ })
      .catch(err => { /* Authentication error */ });
    ```
  </Step>
</Steps>
