Passkey
Add passwordless passkey sign-in and device management to your Solid/Zaidan authentication flow.
The passkey plugin adds passwordless authentication using WebAuthn. Users can sign in with their device authenticator (Touch ID, Face ID, Windows Hello) and manage registered passkeys from their security settings.
It contributes:
- A "Continue with Passkey" button rendered on the sign-in and magic-link views (hidden on sign-up)
- A
<PasskeysSettings />security card for listing, adding, and deleting registered passkeys - Solid query and mutation options such as
signInPasskeyOptions,listPasskeysOptions,addPasskeyOptions, anddeletePasskeyOptions
Setup
Install the Better Auth plugin
Install the @better-auth/passkey package and add it to your Better Auth server config:
import { betterAuth } from "better-auth"
import { passkey } from "@better-auth/passkey"
export const auth = betterAuth({
// ...
plugins: [
passkey()
]
})Configure the WebAuthn origin and trusted origins before production so browser authenticators can complete ceremonies on your real domain.
Install the matching client plugin
Add passkeyClient() to your auth client so authClient.signIn.passkey and authClient.passkey.* are available:
import { createAuthClient } from "@better-auth-ui/solid"
import { passkeyClient } from "@better-auth/passkey/client"
export const authClient = createAuthClient({
plugins: [passkeyClient()]
})Install the UI plugin
Run the shadcn CLI to install the passkey button, passkey management card, and the local passkeyPlugin() factory into your project:
npx shadcn@latest add https://better-auth-ui.com/r/solid/passkey.jsonThis drops the following into your codebase:
src/lib/auth/passkey-plugin.ts—passkeyPlugin()factorysrc/components/auth/passkey/passkey-localization.ts— local localization resolver for copied Passkey UIsrc/components/auth/passkey/passkey-button.tsx— the "Continue with Passkey" sign-in buttonsrc/components/auth/passkey/passkeys.tsx— the passkey management cardsrc/components/auth/passkey/passkey.tsx— individual passkey rowsrc/components/auth/passkey/passkey-skeleton.tsx— loading placeholder shown while passkeys loadsrc/components/auth/passkey/passkeys-empty.tsx— empty state shown when no passkeys existsrc/components/auth/passkey/add-passkey-dialog.tsx— dialog for registering a new passkeysrc/components/auth/passkey/delete-passkey-dialog.tsx— confirmation dialog for deleting a passkeysrc/components/ui/spinner.tsx— pending indicator reused by Passkey actions
Register the plugin
Pass passkeyPlugin() to <AuthProvider>. The local plugin registers the auth-flow button and the security settings card.
import { AuthProvider } from "@/components/auth/auth-provider"
import { passkeyPlugin } from "@/lib/auth/passkey-plugin"
import { authClient } from "@/lib/auth-client"
export function Providers(props: { children?: JSX.Element }) {
return (
<AuthProvider
authClient={authClient}
navigate={navigate}
plugins={[passkeyPlugin()]}
>
{props.children}
</AuthProvider>
)
}When the plugin is registered, <SignIn /> can render <PasskeyButton />, and <SecuritySettings /> can render <PasskeysSettings /> through plugin-contributed securityCards.
Runtime prerequisites
- Server:
passkey()from@better-auth/passkey. - Client:
passkeyClient()from@better-auth/passkey/client. - Production: set the WebAuthn origin and trusted origins correctly.
- Runtime API: passkey options and queries from Solid runtime APIs.
Copied files
src/lib/auth/passkey-plugin.tssrc/components/auth/passkey/passkey-localization.tssrc/components/auth/passkey/passkey-button.tsxsrc/components/auth/passkey/passkeys.tsxsrc/components/auth/passkey/passkey.tsxsrc/components/auth/passkey/passkey-skeleton.tsxsrc/components/auth/passkey/passkeys-empty.tsxsrc/components/auth/passkey/add-passkey-dialog.tsxsrc/components/auth/passkey/delete-passkey-dialog.tsxsrc/components/ui/spinner.tsx
Components
These previews use seeded Storybook fixtures and mocked Passkey methods. They do not call navigator.credentials or start real WebAuthn ceremonies.
<SignIn />
A "Continue with Passkey" button is automatically rendered on the <SignIn /> and magic-link views when the plugin is registered. It is hidden on sign-up.
Usage
import { PasskeyButton } from "@/components/auth/passkey/passkey-button"
export function PasskeySignInDemo() {
return <PasskeyButton />
}Props
<PasskeysSettings />
The <PasskeysSettings /> security card is rendered on the security settings page when your layout renders each plugin's securityCards and passkeyPlugin() is registered. It lists registered passkeys and provides add / delete dialogs.
Usage
import { PasskeysSettings } from "@/components/auth/passkey/passkeys"
export function PasskeysDemo() {
return <PasskeysSettings />
}Props
Prop
Type
Options
passkeyPlugin({
// Override any of the plugin's localization strings.
localization: {
passkeys: "Security Keys"
}
})Prop
Type
Localization
Prop
Type
The copied Zaidan Passkey components resolve labels from passkeyPlugin({ localization }) and fall back to the default Passkey localization.
Solid runtime APIs
Use the low-level Solid runtime helpers when building custom Passkey UI:
Last updated on