Docs

Build a custom authentication flow using passkeys

Warning

This guide is for users who want to build a custom user interface using the Clerk API. To authenticate users with passkeys using a prebuilt UI, you should use Clerk's <SignIn /> and <UserProfile /> components. See the sign-in and sign-out options docs to learn more.

Clerk supports passwordless authentication via passkeys, enabling users to sign in without having to remember a password. Instead, users select a passkey associated with their device, which they can use to authenticate themselves.

This guide will teach you how to use Clerk's API to build custom flows for creating, signing users in with, and managing passkeys.

Create user passkeys

To create a passkey for a user, you must call User.createPasskey() as shown in the following example:

app/components/CustomCreatePasskeysButton.tsx
"use client";

import { useUser } from "@clerk/nextjs";

export function CreatePasskeyButton() {
  const { user } = useUser();

  const createClerkPasskey = async () => {
    try {
      const response = await user?.createPasskey();
      console.log(response);
    } catch (error: ClerkRuntimeError) {
      console.log(error);
    }
  };

  return (
    <button onClick={createClerkPasskey}>
      Create a passkey now
    </button>
  );
}

Sign a user in with a passkey

To sign a user into your Clerk app with a passkey, you must call SignIn.authenticateWithPasskey(). This method allows users to choose from their discoverable passkeys, such as hardware keys or passkeys in password managers.

components/SignInWithPasskeyButton.tsx
"use client";

import { useSignIn } from "@clerk/nextjs";

export function SignInWithPasskeyButton() {
  const { signIn } = useSignIn();

  const signInWithPasskey = async () => {
    // 'discoverable' lets the user choose a passkey
    // without autofilling any of the options
    try {
      const response = await signIn?.authenticateWithPasskey();
      console.log(response);
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <button onClick={signInWithPasskey}>
      Sign in with a passkey
    </button>
  );
}

Rename user passkeys

Clerk generates a name based on the device associated with the passkey when it's created. Sometimes users may want to rename a passkey to make it easier to identify.

To rename a user's passkey in your Clerk app, you must call the update() method of the passkey object as shown in the following example:

components/RenamePasskeyUI.tsx
"use client";

import { useUser } from "@clerk/nextjs";
import { useRef, useState } from "react";

export function RenamePasskeyUI() {
  const { user } = useUser();
  const passkeyToUpdateId = useRef<HTMLInputElement>(null);
  const newPasskeyName = useRef<HTMLInputElement>(null);
  const { passkeys } = user;
  const [success, setSuccess] = useState(false);

  const renamePasskey = async () => {
    try {
      const passkeyToUpdate = passkeys?.find(
        (pk: PasskeyResource) => pk.id === passkeyToUpdateId.current?.value
      );
      const response = await passkeyToUpdate?.update({
        name: newPasskeyName.current?.value,
      });
      console.log(response);
      setSuccess(true);
    } catch (error) {
      console.log(error);
      setSuccess(false);
    }
  };

  return (
    <>
      <p>Passkeys:</p>
      <ul>
        {passkeys?.map((pk: PasskeyResource) => {
          return (
            <li key={pk.id}>
              Name: {pk.name} | ID: {pk.id}
            </li>
          );
        })}
      </ul>
      <input 
        ref={passkeyToUpdateId}
        type="text"
        placeholder="ID of passkey to update"
      />
      <input type="text" placeholder="New name" ref={newPasskeyName} />
      <button onClick={renamePasskey}>Rename your passkey</button>
      <p>Passkey updated: {success ? "Yes" : "No"}</p>
    </>
  );
}

Delete user passkeys

To delete a user's passkey from your Clerk app, you must call the delete() method of the passkey object as shown in the following example:

components/DeletePasskeyUI.tsx
"use client";

import { useUser } from "@clerk/nextjs";
import { useRef, useState } from "react";

export function DeletePasskeyUI() {
  const { user } = useUser();
  const passkeyToDeleteId = useRef<HTMLInputElement>(null);
  const { passkeys } = user;
  const [success, setSuccess] = useState(false);

  const deletePasskey = async () => {
    const passkeyToDelete = passkeys?.find(
      (pk: any) => pk.id === passkeyToDeleteId.current?.value
    );
    try {
      const response = await passkeyToDelete?.delete();
      console.log("Response", response);
      setSuccess(true);
    } catch (error) {
      console.log("Error", error);
      setSuccess(false);
    }
  };

  return (
    <>
      <p>Passkeys:</p>
      <ul>
        {passkeys?.map((pk: any) => {
          return (
            <li key={pk.id}>
              Name: {pk.name} | ID: {pk.id}
            </li>
          );
        })}
      </ul>
      <input
        ref={passkeyToDeleteId}
        type="text"
        placeholder="ID of passkey to delete"
      />
      <button onClick={deletePasskey}>Delete passkey</button>
      <p>Passkey deleted: {success ? "Yes" : "No"}</p>
    </>
  );
}

Feedback

What did you think of this content?