BTST
PluginsQuickstartDocs
Live Blog

From research to product evaluation

Evaluating a publishing workflow for an app you already own?

See what the BTST Blog plugin adds to an existing React or Next.js app
BTST

Open-source TypeScript features for the React application, data, and deployment you already own.

Released plugins

  • Blog
  • AI Chat
  • CMS
  • Form Builder
  • UI Builder
  • Kanban
  • Comments
  • Media
  • Route Docs
  • OpenAPI
  • Better Auth UI

Resources

  • Quickstart
  • Documentation
  • All plugins
  • Live Blog
  • GitHub (opens in a new tab)
© 2026 BTST. Open source under the MIT License.
AI Chat
September 17, 2026ReactBetter Auth UI

Add Passkey Sign-In to BTST Better Auth UI

Configure the relying-party domain, schema, browser client, and account security controls before verifying enrollment and recovery.

Add Passkey Sign-In to BTST Better Auth UI

Passkey support in BTST connects a Better Auth server plugin, a browser plugin, and the account security interface. The UI flag reveals the controls; the server verifies registration and sign-in against your application's domain and origin.

This guide targets @btst/better-auth-ui@2.0.0, Better Auth 1.6.16, and @better-auth/passkey@1.6.16. It extends the existing auth integration. Keep the compatible dependency group together rather than independently upgrading one auth package.

Fix the relying-party configuration first#

A passkey belongs to a relying party, identified by a domain. For an application served entirely from https://app.example.com, add this plugin to the existing server configuration:

TS
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
import { passkey } from "@better-auth/passkey";

export const passkeyPlugin = passkey({
  rpID: "app.example.com",
  rpName: "Example App",
  origin: "https://app.example.com",
});

Append passkeyPlugin to betterAuth({ plugins: [...] }), preserving your database, existing auth methods, trusted origins, and handler. Replace the example values with the actual deployed configuration. rpID is a hostname without a scheme, port, or path; origin includes the scheme and any nondefault port, with no route path or trailing slash.

For a local app at http://localhost:3000, use rpID: "localhost" and origin: "http://localhost:3000". Treat production and development credentials as separate. Do not assume a passkey created for one domain will work on an unrelated preview hostname. Choose the production domain before enrolling users; a domain change needs a deliberate migration and recovery plan.

The Better Auth passkey options describe the domain and origin configuration. Browser Web Authentication uses public-key credentials and requires a secure context, with local development exceptions. The application does not receive a fingerprint or face scan; the authenticator handles its local verification method.

Apply the plugin's schema#

The passkey plugin stores credential records. Generate the schema with the Better Auth tooling matching your installed release, review the resulting changes, and apply them through your existing database migration process. Confirm the auth adapter points to the updated schema before exposing enrollment.

Adding a client button does not create this table. A missing-table error during registration is a database configuration problem, not a reason to remove server checks. Keep migrations specific to the intended environment and preserve existing account, session, and credential data.

Add the browser plugin and enable the controls#

Add the passkey client plugin to the same auth client BTST uses:

TS
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
import { createAuthClient } from "better-auth/react";
import { passkeyClient } from "@better-auth/passkey/client";

export const authClient = createAuthClient({
  plugins: [passkeyClient()],
});

This is a minimal fragment. Preserve other plugins and your configured API origin or base path. Then merge this into StackProvider's existing overrides.auth configuration:

TS
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
import type { AuthPluginOverrides } from "@btst/better-auth-ui/client";

export const passkeyUI = {
  passkey: true,
} satisfies Partial<AuthPluginOverrides>;

The released security page includes the passkey card when this option is enabled. With default paths and a /p site mount, open /p/account/security after signing in through an existing method. The card lists registered credentials and calls authClient.passkey.addPasskey when adding one.

Enrollment normally requires a fresh session. The companion checks its freshAge before opening the browser prompt; the server separately enforces its session freshness requirement. Keep the UI age aligned with your existing server session.freshAge policy. If enrollment asks for a new sign-in, reauthenticate through a supported method rather than weakening the server requirement.

The sign-in button calls authClient.signIn.passkey. Successful authentication then uses the provider's normal success transition. Preserve session refresh and backend permission checks; possessing a credential does not grant every application role.

Test enrollment and sign-in separately#

Use a controlled account on the actual intended origin:

  1. Sign in through the current method and add a passkey from account security.
  2. Confirm the credential appears in the list and that cancelling a second browser prompt leaves the account usable.
  3. Sign out, use the passkey button, and verify both the visible session and a protected server request.
  4. Delete a test credential, then confirm it cannot complete another sign-in. Check other enrolled credentials still work.
  5. Exercise recovery from a browser or device without access to that credential before removing any fallback sign-in method.
SymptomFirst checks
No passkey button or cardpasskey override and the mounted auth/account pages
Client method is missingpasskeyClient() on the provider's actual client
Enrollment rejected before the promptSession presence, freshness, database schema
Origin or relying-party errorBrowser address, configured origin, RP ID, preview hostname
Prompt cancelled or no credential availableBrowser/authenticator support and an existing recovery method

Support and credential synchronization vary by browser, operating system, authenticator, and user settings. Keep recovery usable and verify your deployment with representative devices. The configuration snippets were type-checked; no physical-authenticator or production enrollment round trip was performed for this article.

Continue with the BTST auth documentation and the session-management guide to check what happens after sign-in or revocation.

In This Post

Fix the relying-party configuration firstApply the plugin's schemaAdd the browser plugin and enable the controlsTest enrollment and sign-in separately