new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Better Auth · Plugins · all subjects

passkey plugin

25 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Passkey plugin for WebAuthn authentication

Better Auth includes a Passkey plugin that provides WebAuthn/passkey authentication support.

Passkey plugin installation

Install the passkey plugin using `npm install @better-auth/passkey`. Import the plugin from `@better-auth/passkey` and add it to the plugins array in the betterAuth configuration. Run `npx auth migrate` or `npx auth generate` to update the database schema.

Passkey client setup

Import `passkeyClient` from `@better-auth/passkey/client` and add it to the createAuthClient plugins array. This enables client-side passkey methods.

Passkey registration configuration options

The passkey plugin accepts a configuration object with registration and authentication options. Registration options include: requireSession (boolean, default true; set false for passkey-first onboarding), resolveUser (async function required if requireSession is false, receives ctx and context parameters and returns user object with id and name fields), and extensions (optional server-defined extensions like credProps: true). Authentication options include extensions (optional server-defined extensions).

Passkey-first registration without session

When registration.requireSession is false, passkey registration can be initiated without a session. Pass an opaque context string to auth.api.generatePasskeyRegistrationOptions({ context: 'signed-registration-token' }). From the client, call authClient.passkey.addPasskey({ context: 'signed-registration-token', createSession: true }) with the same context so the server can resolve the user during verification.

addPasskey method parameters

The addPasskey method accepts: name (optional string, defaults to user's email or ID), authenticatorAttachment (optional 'platform' or 'cross-platform', default allows both), extensions (optional WebAuthn extensions like credProps or largeBlob), returnWebAuthnResponse (optional boolean to return WebAuthn response and extension results), context (optional string for passkey-first registration flows), and createSession (optional boolean to create a session after successful registration, response includes session and user when enabled).

signIn.passkey method parameters

The signIn.passkey method accepts: autoFill (optional boolean, default true, enables browser autofill/Conditional UI), extensions (optional WebAuthn extensions), and returnWebAuthnResponse (optional boolean to return WebAuthn response and extension results).

Sign in with passkey example

Example usage: await authClient.signIn.passkey({ autoFill: true, extensions: { credProps: true }, fetchOptions: { onSuccess(context) { window.location.href = '/dashboard'; }, onError(context) { console.error('Authentication failed:', context.error.message); } } });

Passkey extensions and WebAuthn response

Pass extensions through the extensions parameter in addPasskey or signIn.passkey methods. When returnWebAuthnResponse is true, the client returns webauthn.clientExtensionResults containing the extension results. Example: const result = await authClient.passkey.addPasskey({ extensions: { credProps: true }, returnWebAuthnResponse: true }); console.log(result.webauthn?.clientExtensionResults);

List passkeys API endpoint

The listUserPasskeys method is a GET endpoint at /passkey/list-user-passkeys that requires a session. It takes no parameters and returns a list of all passkeys for the authenticated user. Returned passkeys include aaguid field containing the authenticator model identifier.

Authenticator naming with getAuthenticatorName

The plugin exports getAuthenticatorName function that takes an aaguid string and returns a friendly name for the authenticator model. When a user registers a passkey without naming it, the name field is left empty; use getAuthenticatorName(passkey.aaguid) in the UI to show a default. The built-in list is small and not authoritative. Extend it by spreading the exported commonAuthenticatorNames map: const names = { ...commonAuthenticatorNames, 'your-aaguid': 'Your Provider' };

Setting default passkey name at registration

Return a name from registration.afterVerification to set a default label on the server at registration time. The AAGUID is available on verification.registrationInfo?.aaguid. Client-supplied names always take precedence. Example: afterVerification: async ({ verification }) => ({ name: getAuthenticatorName(verification.registrationInfo?.aaguid) })

Delete passkey API endpoint

The deletePasskey method is a POST endpoint at /passkey/delete-passkey that requires a session. It accepts id parameter (string, required) containing the ID of the passkey to delete.

Update passkey name API endpoint

The updatePasskey method is a POST endpoint at /passkey/update-passkey that requires a session. It accepts id parameter (string, required) containing the ID of the passkey to update and name parameter (string, required) containing the new name for the passkey.

Conditional UI requirements

Conditional UI allows the browser to autofill passkeys. Two requirements: (1) Add autocomplete attribute with value 'webauthn' to input fields (webauthn must be the last entry); (2) Preload passkeys by calling authClient.signIn.passkey({ autoFill: true }) when component mounts. First check if conditional UI is supported using PublicKeyCredential.isConditionalMediationAvailable(). Some browsers require user interaction with input field before autofill prompt appears.

Conditional UI example implementation

useEffect(() => { if (!PublicKeyCredential.isConditionalMediationAvailable || !PublicKeyCredential.isConditionalMediationAvailable()) { return; } void authClient.signIn.passkey({ autoFill: true }) }, [])

Passkey table schema

The passkey table stores credentials with fields: id (string, primary key, unique identifier), name (string, optional, name of passkey), publicKey (string, public key), userId (string, foreign key to user.id), credentialID (string, unique identifier of registered credential), counter (number, counter value), deviceType (string, type of device used), backedUp (boolean, whether backed up), transports (string, optional, transports used), createdAt (Date, optional, creation timestamp), aaguid (string, optional, Authenticator's Attestation GUID indicating authenticator type).

Passkey plugin required options

Required server configuration options: rpID (unique identifier for website based on auth server origin, e.g., 'localhost' for local dev, or 'example.com' for www.example.com), rpName (human-readable title for website), origin (origin URL where better-auth server is hosted, e.g., http://localhost or http://localhost:PORT, do not include trailing /).

Authenticator selection options

authenticatorSelection option customizes WebAuthn authenticator selection. authenticatorAttachment can be 'platform' (attached to platform, e.g., fingerprint reader) or 'cross-platform' (not attached, e.g., security key), defaults to not set (both allowed, platform preferred). residentKey can be 'required' (MUST store on authenticator, highest security), 'preferred' (encouraged but not mandatory, default), or 'discouraged' (no storage, fastest). userVerification can be 'required' (MUST verify identity, highest security), 'preferred' (encouraged but not mandatory, default), or 'discouraged' (no verification, fastest).

Passkey advanced options

Advanced configuration: webAuthnChallengeCookie (cookie name for storing WebAuthn challenge ID during authentication flow, default 'better-auth-passkey').

Passkey plugin with Expo configuration

When using passkey plugin with Expo, configure the cookiePrefix option in the Expo client to ensure passkey cookies are properly detected. By default, passkey plugin uses 'better-auth-passkey' as challenge cookie name. Since it starts with 'better-auth', it works with default Expo client configuration. If customizing webAuthnChallengeCookie, also update cookiePrefix in Expo client. If using custom cookie name 'my-app-passkey' on server, set cookiePrefix: 'my-app' in Expo client. For multiple systems, provide array: cookiePrefix: ['better-auth', 'my-app', 'custom-auth']. If cookiePrefix doesn't match webAuthnChallengeCookie prefix, passkey authentication fails.

Expo passkey configuration example

Server configuration: import { betterAuth } from 'better-auth'; import { passkey } from '@better-auth/passkey'; export const auth = betterAuth({ plugins: [ passkey({ advanced: { webAuthnChallengeCookie: 'my-app-passkey' } }) ] }); Client configuration: import { createAuthClient } from 'better-auth/react'; import { expoClient } from '@better-auth/expo/client'; import { passkeyClient } from '@better-auth/passkey/client'; import * as SecureStore from 'expo-secure-store'; export const authClient = createAuthClient({ baseURL: 'http://localhost:8081', plugins: [ expoClient({ storage: SecureStore, cookiePrefix: 'my-app' }), passkeyClient() ] });

Passkey throw option behavior

Setting throw: true in fetch options has no effect for register and sign-in passkey responses. They always return a data object containing the error object instead of throwing.

Passkey implementation library

The passkey plugin implementation is powered by SimpleWebAuthn (https://simplewebauthn.dev/) behind the scenes.

What is a passkey

Passkeys are a secure, passwordless authentication method using cryptographic key pairs, supported by WebAuthn and FIDO2 standards in web browsers. They replace passwords with unique key pairs: a private key stored on the user's device and a public key shared with the website. Users can log in using biometrics, PINs, or security keys, providing strong, phishing-resistant authentication without traditional passwords.

Give your agent this brain