Skip to main content

methods/username-passkey/react

React client for the usernamePasskey method, exported at @convex-dev/auth/methods/username-passkey/react.

useSignInWithUsernamePasskey is the batteries-included hook for the username + passkey login form. It drives the browser-side WebAuthn ceremonies (through the internal client module built on @simplewebauthn/browser) against the mutations of a passkey recipe, and it handles both the identifier-first modal flow and passkey autofill (conditional mediation), where the user selects an account directly in the autocompletion list.

useAddPasskey and useRemovePasskey (from management/react.tsx) drive the passkey settings page of a signed-in user.

Interfaces​

Type Aliases​

AddPasskeyApi​

type AddPasskeyApi = object;

Properties​

PropertyType
finishAddPasskeyFinishAddPasskeyMutation
startAddPasskeyStartAddPasskeyMutation
verifyAddPasskeyVerifyAddPasskeyMutation

AddPasskeyResult​

type AddPasskeyResult =
| AddPasskeyFlowResult
| AlreadyPendingFailure;

AlreadyPendingFailure​

type AlreadyPendingFailure = object;

Failure shape for AlreadyPendingError.

Properties​

PropertyType
successfalse
userErrorAlreadyPendingError

PasskeyAutofillStatus​

type PasskeyAutofillStatus = "idle" | "waiting" | "signingIn" | "signedIn" | "stopped";

The states of usePasskeyAutofill:

  • "idle": the flow has not started yet, or autofill is off.
  • "waiting": the autofill request is pending in the browser.
  • "signingIn": a picked assertion is being handled (onAssertion is running, usually a server verification).
  • "signedIn": onAssertion reported success and the flow is done.
  • "stopped": the flow is done without a success (failure, or no browser support).

PasskeyClientError​

type PasskeyClientError =
| {
error: "CEREMONY_ABORTED";
}
| {
error: "PASSKEY_ALREADY_REGISTERED";
}
| {
error: "WEBAUTHN_UNSUPPORTED";
}
| {
cause: unknown;
error: "OTHER_ERROR";
};

The failures the browser side produces (the server never returns these):

  • CEREMONY_ABORTED: the user dismissed the passkey dialog, the browser refused the ceremony (NotAllowedError), or another ceremony displaced this one. This is the most common failure; show a calm "sign-in was cancelled" message.
  • PASSKEY_ALREADY_REGISTERED: the authenticator refused a registration because it already holds a passkey for this account (InvalidStateError, usually via excludeCredentials). Tell the user they can sign in with the passkey they already have.
  • WEBAUTHN_UNSUPPORTED: the browser has no WebAuthn support, or the page is not a secure context.
  • OTHER_ERROR: everything else thrown (a network blip, a bug, an unexpected server error). The thrown value is preserved on cause for callers that want to inspect or log it.

PasskeyClientFailure​

type PasskeyClientFailure = object;

A failed result with a browser-side userError. The failure arm all the ceremony functions and hooks share; it composes with the server's own { success: false, userError } results into one userError switch.

Properties​

PropertyType
successfalse
userErrorPasskeyClientError

RemovePasskeyApi​

type RemovePasskeyApi = object;

Properties​

PropertyType
finishRemovePasskeyFinishRemovePasskeyMutation
startRemovePasskeyStartRemovePasskeyMutation

RemovePasskeyResult​

type RemovePasskeyResult =
| RemovePasskeyFlowResult
| AlreadyPendingFailure;

RenamePasskeyResult​

type RenamePasskeyResult = Infer<typeof renamePasskeyResult>;

SignInWithUsernamePasskeyResult​

type SignInWithUsernamePasskeyResult =
| SignInFlowResult
| SignInError<PasskeyClientError | AlreadyPendingError>;

The result of the signIn callback from useSignInWithUsernamePasskey.

A completed sign-in carries a flow discriminant: "signUp" when the ceremony created a new account, "signIn" when it authenticated an existing one. ALREADY_PENDING comes back when a signIn call runs while the previous one still does, or while an autofill sign-in is verified.


UsernamePasskeyApi​

type UsernamePasskeyApi = object;

The mutation references the sign-in flows drive.

Properties​

PropertyType
finishSignInWithUsernamePasskeyFinishSignInMutation
finishSignUpWithUsernamePasskeyFinishSignUpMutation
startAutofillSignInWithUsernamePasskeyStartAutofillSignInMutation
startSignInWithUsernamePasskeyStartSignInMutation

UsernamePasskeyAutofillError​

type UsernamePasskeyAutofillError =
| Extract<FinishSignInResult, {
status: "error";
}>["userError"]
| PasskeyClientError;

The errors the autofill sign-in flow reports.


WireAuthenticationResponse​

type WireAuthenticationResponse = Infer<typeof vAuthenticationResponseJSON>;

WireCreationOptions​

type WireCreationOptions = Infer<typeof vPublicKeyCredentialCreationOptionsJSON>;

WireRegistrationResponse​

type WireRegistrationResponse = Infer<typeof vRegistrationResponseJSON>;

WireRequestOptions​

type WireRequestOptions = Infer<typeof vPublicKeyCredentialRequestOptionsJSON>;

Functions​

useAddPasskey()​

function useAddPasskey(managementApi): object;

Hook for the "Add a passkey" button of a settings page.

import { useAddPasskey } from "@convex-dev/auth/methods/username-passkey/react";
import { api } from "../convex/_generated/api";

function AddPasskeyButton() {
const { addPasskey, pending } = useAddPasskey(api.auth);
return (
<button
disabled={pending}
onClick={async () => {
const result = await addPasskey();
if (!result.success) {
// map result.userError to a message
}
}}
>
Add a passkey
</button>
);
}

First asks the user to authenticate with an existing passkey to prove their identity, then registers a new passkey.

Parameters​

ParameterTypeDescription
managementApiAddPasskeyApiThe app module that re-exports the passkey-management functions of the method, for example api.auth.

Returns​

addPasskey​
addPasskey: () => Promise<AddPasskeyResult>;

Adds a passkey to the account of the signed-in user.

Returns an object with a success boolean flag. If it is false, the object has a userError field that tells why.

Returns​

Promise<AddPasskeyResult>

pending​
pending: boolean;

true while an addPasskey call is running.


useRemovePasskey()​

function useRemovePasskey(managementApi): object;

Hook for the "Remove" button of a passkey list.

import { useRemovePasskey } from "@convex-dev/auth/methods/username-passkey/react";
import { api } from "../convex/_generated/api";

function RemovePasskeyButton({ passkeyId }: { passkeyId: string }) {
const { removePasskey, pending } = useRemovePasskey(api.auth);
return (
<button
disabled={pending}
onClick={async () => {
const result = await removePasskey(passkeyId);
if (!result.success) {
// map result.userError to a message
}
}}
>
Remove
</button>
);
}

This asks the user to reauthenticate with another passkey before deleting the targeted passkey.

Parameters​

ParameterTypeDescription
managementApiRemovePasskeyApiThe app module that re-exports the passkey-management functions of the method, for example api.auth.

Returns​

pending​
pending: boolean;

true while a removePasskey call is running.

removePasskey​
removePasskey: (passkeyId) => Promise<RemovePasskeyResult>;

Removes the given passkey from the account of the signed-in user.

Returns an object with a success boolean flag. If it is false, the object has a userError field that tells why.

Parameters​
ParameterType
passkeyIdstring
Returns​

Promise<RemovePasskeyResult>


useSignInWithUsernamePasskey()​

function useSignInWithUsernamePasskey(usernamePasskeyApi): object;

Client for the log in page in the “username + passkey” auth flow.

This client handles both:

  • Manually logging in by providing an identifier (e.g. username) then registering a new passkey or logging in
  • Passkey autofill (conditional mediation) where the user selects an account directly in the autocompletion list.
import { useSignInWithUsernamePasskey } from "@convex-dev/auth/methods/username-passkey/react";
import { useState } from "react";
import { api } from "../convex/_generated/api";

function LogIn() {
// While this hook is mounted, the browser also shows the stored passkeys
// in the autocompletion list of the username field.
const { signIn, pending, autofill } = useSignInWithUsernamePasskey(api.auth);
const [username, setUsername] = useState("");
const [error, setError] = useState<string | null>(null);

// The alerts show the error codes. Map each code to a message for the user.
return (
<form
onSubmit={async (e) => {
e.preventDefault();
setError(null);
const result = await signIn({ username });
if (result.status === "error") {
setError(result.userError.error);
}
}}
>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
autoComplete="username webauthn"
required
disabled={pending}
/>
{error !== null && <p role="alert">{error}</p>}
{autofill.lastError !== null && (
<p role="alert">{autofill.lastError.error}</p>
)}
<button type="submit" disabled={pending}>
{pending ? "Waiting for your passkey…" : "Continue with a passkey"}
</button>
</form>
);
}

Parameters​

ParameterTypeDescription
usernamePasskeyApiUsernamePasskeyApiThe functions of the method, usually api.auth.

Returns​

autofill​
autofill: object;

The state of the autofill flow:

  • available: whether the browser can offer passkeys in the autocompletion list; null while the detection runs.
  • status: the state of the request; see PasskeyAutofillStatus.
  • lastError: the most recent failure, for display or logging.
autofill.available​
available: boolean | null;
autofill.lastError​
lastError:
| {
error: "PROTOCOL_ERROR";
}
| {
error: "UNKNOWN_CREDENTIAL";
}
| {
error: "CHALLENGE_EXPIRED";
}
| {
error: "CEREMONY_ABORTED";
}
| {
error: "PASSKEY_ALREADY_REGISTERED";
}
| {
error: "WEBAUTHN_UNSUPPORTED";
}
| {
cause: unknown;
error: "OTHER_ERROR";
}
| null;
autofill.status​
status: PasskeyAutofillStatus;
pending​
pending: boolean;

true while a sign-in runs: a signIn call, or an autofill sign-in that the server verifies (autofill.status is "signingIn"). A signIn call in this time returns ALREADY_PENDING.

signIn​
signIn: (__namedParameters) => Promise<SignInWithUsernamePasskeyResult>;

Runs the identifier-first passkey flow for the given username.

Returns an object with a status field.

If it is "complete" the sign-in (or the account creation) was successful and the client will establish an authenticated session with the Convex backend server. The flow field tells which one it was: "signUp" created a new account, "signIn" authenticated an existing one.

If it is "error" the returned object will have a userError field with additional details about why sign-in failed.

Parameters​
ParameterType
__namedParameters{ username: string; }
__namedParameters.usernamestring
Returns​

Promise<SignInWithUsernamePasskeyResult>