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

# Passkey sign-in & registration

> Let returning users sign in with Face ID, a fingerprint, or a security key, and manage their registered passkeys.

A passkey is a passwordless credential stored on the user's device (or password manager). The user signs in with the same biometric or PIN they use to unlock their device — no password, nothing to remember.

<Warning>
  **Passkeys are a sign-*in* method, not a sign-*up* method.** A user cannot create an account with a passkey. The order is always:

  1. The user **signs up** with another method (email/SMS OTP, social, wallet).
  2. While **authenticated**, they **register** a passkey.
  3. On a later visit, they **sign in** with that passkey.

  `registerPasskey` requires an authenticated user; `signInWithPasskey` requires an already-registered passkey. Build your UI around that sequence.
</Warning>

## Prerequisites

Before this: create and initialize a Dynamic client (see [Creating a Dynamic Client](/javascript/reference/client/create-dynamic-client), [Initializing the Dynamic Client](/javascript/reference/client/initialize-dynamic-client)).

Enable **Passkey** in your [Dynamic dashboard](https://app.dynamic.xyz/dashboard/log-in-user). Passkeys use WebAuthn, which requires a secure context (HTTPS, or `localhost` in development).

## Register a passkey

Call `registerPasskey` while the user is signed in — for example, in an "enable faster sign in" prompt right after sign-up, or from an account-security screen. It triggers the browser's passkey-creation dialog, then stores the credential against the user.

<Tabs>
  <Tab title="TypeScript">
    Wire the call to your "enable faster sign in" button. Disable it while the browser dialog is open so the user can't trigger two prompts at once.

    ```typescript theme={"system"}
    import { registerPasskey } from '@dynamic-labs-sdk/client';

    const button = document.querySelector<HTMLButtonElement>('#add-passkey')!;

    button.addEventListener('click', async () => {
      button.disabled = true;
      try {
        // The browser prompts the user to create a passkey (Face ID, fingerprint, etc.)
        await registerPasskey();
        // showConfirmation is your implementation.
        showConfirmation('Passkey added.');
      } finally {
        button.disabled = false;
      }
    });
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import { useRegisterPasskey, useGetPasskeys } from '@dynamic-labs-sdk/react-hooks';

    function AddPasskeyButton() {
      const { mutate: registerPasskey, isPending } = useRegisterPasskey();
      const { refetch } = useGetPasskeys();

      return (
        <button
          disabled={isPending}
          onClick={() => registerPasskey(undefined, { onSuccess: () => refetch() })}
        >
          {isPending ? 'Follow the prompt…' : 'Set up a passkey'}
        </button>
      );
    }
    ```
  </Tab>
</Tabs>

## Sign in with a passkey

For returning users who already registered a passkey, a single button is enough. `signInWithPasskey` opens the browser's passkey picker and, on success, authenticates the user.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"system"}
    import { signInWithPasskey } from '@dynamic-labs-sdk/client';

    const button = document.querySelector<HTMLButtonElement>('#passkey-sign-in')!;

    button.addEventListener('click', async () => {
      // Opens the browser's passkey picker; resolves with the authenticated user.
      const { user } = await signInWithPasskey();
      // renderApp is your implementation.
      renderApp(user);
    });
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import { useSignInWithPasskey, useUser } from '@dynamic-labs-sdk/react-hooks';

    function PasskeySignInButton() {
      const { data: user } = useUser();
      const { mutate: signInWithPasskey, isPending, error } = useSignInWithPasskey();

      if (user) return <p>Signed in as {user.email}</p>;

      return (
        <>
          <button disabled={isPending} onClick={() => signInWithPasskey()}>
            Sign in with a passkey
          </button>
          {error && <p role="alert">Couldn't sign in with a passkey. Try another method.</p>}
        </>
      );
    }
    ```
  </Tab>
</Tabs>

<Tip>
  Offer "Sign in with a passkey" alongside your other sign-in options rather than as the only choice — a returning user might be on a device where they haven't registered one yet.
</Tip>

## List and remove passkeys

Let users see and manage their registered passkeys from an account-security screen. `getPasskeys` returns the user's passkeys; `deletePasskey` removes one by `id`. Each passkey has an optional user-assigned `alias`, plus `createdAt` and the `origin` it was registered on.

<Tabs>
  <Tab title="TypeScript">
    Render the user's passkeys into a list, each with a "Remove" button that calls `deletePasskey` and re-renders.

    ```typescript theme={"system"}
    import { getPasskeys, deletePasskey } from '@dynamic-labs-sdk/client';

    const list = document.querySelector<HTMLUListElement>('#passkey-list')!;

    async function renderPasskeys() {
      const passkeys = await getPasskeys();
      list.replaceChildren();

      for (const passkey of passkeys) {
        const item = document.createElement('li');
        const added = new Date(passkey.createdAt).toLocaleDateString();
        item.textContent = `${passkey.alias ?? 'Passkey'} · added ${added} `;

        const remove = document.createElement('button');
        remove.textContent = 'Remove';
        remove.addEventListener('click', async () => {
          await deletePasskey({ passkeyId: passkey.id });
          renderPasskeys();
        });

        item.append(remove);
        list.append(item);
      }
    }

    renderPasskeys();
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import { useGetPasskeys, useDeletePasskey } from '@dynamic-labs-sdk/react-hooks';

    function PasskeyList() {
      const { data: passkeys, isLoading, refetch } = useGetPasskeys();
      const { mutate: deletePasskey } = useDeletePasskey();

      if (isLoading) return <p>Loading passkeys…</p>;
      if (!passkeys?.length) return <p>No passkeys yet.</p>;

      return (
        <ul>
          {passkeys.map((passkey) => (
            <li key={passkey.id}>
              {passkey.alias ?? 'Passkey'} · added{' '}
              {new Date(passkey.createdAt).toLocaleDateString()}
              <button
                onClick={() =>
                  deletePasskey({ passkeyId: passkey.id }, { onSuccess: () => refetch() })
                }
              >
                Remove
              </button>
            </li>
          ))}
        </ul>
      );
    }
    ```
  </Tab>
</Tabs>

<Note>
  Removing a passkey is a sensitive action, so it may require the user to re-authenticate (step-up). See [Step-up authentication](/javascript/authentication-methods/step-up-auth/overview) for how to handle that prompt.
</Note>

## Handling errors

| Error                            | Cause                                                           | Resolution                                                                  |
| -------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `NoWebAuthNSupportError`         | The browser or device doesn't support WebAuthn.                 | Hide the passkey option and fall back to another sign-in method.            |
| `NoPasskeyCredentialsFoundError` | Sign-in was attempted but the user has no passkey for this app. | Prompt them to sign in another way, then register a passkey.                |
| Browser cancellation             | The user dismissed the passkey dialog, or it timed out.         | The call rejects — leave the user on the current screen and let them retry. |

```typescript theme={"system"}
import {
  signInWithPasskey,
  NoWebAuthNSupportError,
  NoPasskeyCredentialsFoundError,
} from '@dynamic-labs-sdk/client';

try {
  await signInWithPasskey();
} catch (error) {
  if (error instanceof NoWebAuthNSupportError) {
    // This device can't use passkeys — offer email or social sign-in instead.
  } else if (error instanceof NoPasskeyCredentialsFoundError) {
    // No passkey registered yet — send the user through another method first.
  } else {
    // User cancelled the prompt, or it timed out — let them try again.
  }
}
```

## See also

* [Email & SMS OTP sign-in](/javascript/building-ui/email-sms-otp-sign-in) — a sign-up method users can pair with a passkey
* [Social sign-in & OAuth redirect handling](/javascript/building-ui/social-oauth-redirect-handling) — another sign-up method
* [Authenticate with passkey](/javascript/authentication-methods/passkey) — passkey method reference
* [Step-up authentication](/javascript/authentication-methods/step-up-auth/overview) — re-authenticate for sensitive actions
