> ## Documentation Index
> Fetch the complete documentation index at: https://docs.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.

## Prerequisites

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

Enable **Passkey** in your [Dynamic Console](https://console.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.

> [tab] 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] 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>
>   );
> }
> ```

## 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.

> [tab] 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] 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>}
>     </>
>   );
> }
> ```

> [!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.

If your users registered passkeys on a previous domain, pass `relatedOriginRpId` to `signInWithPasskey` so they can reuse those passkeys here. See [Signing in with a passkey from a related domain](https://docs.dynamic.xyz/docs/javascript/authentication-methods/passkey.md#signing-in-with-a-passkey-from-a-related-domain).

## 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.

> [tab] 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] 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>
>   );
> }
> ```

> [!NOTE]
>
> Removing a passkey is a sensitive action, so it may require the user to re-authenticate (step-up). See [Step-up authentication](https://docs.dynamic.xyz/docs/javascript/authentication-methods/step-up-auth/overview.md) for how to handle that prompt.

## 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](https://docs.dynamic.xyz/docs/javascript/building-ui/email-sms-otp-sign-in.md) — a sign-up method users can pair with a passkey
* [Social sign-in & OAuth redirect handling](https://docs.dynamic.xyz/docs/javascript/building-ui/social-oauth-redirect-handling.md) — another sign-up method
* [Authenticate with passkey](https://docs.dynamic.xyz/docs/javascript/authentication-methods/passkey.md) — passkey method reference
* [Step-up authentication](https://docs.dynamic.xyz/docs/javascript/authentication-methods/step-up-auth/overview.md) — re-authenticate for sensitive actions


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.