mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-10 04:19:32 +00:00
* fix(email-verification): improve email verification logic to check session and user email consistency (#3042) * docs(passkey): Fixed signIn passkey props (#3014) callbackURL doesn't exist. * feat: svelte kit cookie helper * lint * docs and clean up * clean up * lint * clean up * clean up * sync cookies * lint * pruning * pruning and lint * update * update * update --------- Co-authored-by: Bereket Engida <86073083+Bekacru@users.noreply.github.com> Co-authored-by: Maxwell <145994855+ping-maxwell@users.noreply.github.com>
263 lines
9.7 KiB
Plaintext
263 lines
9.7 KiB
Plaintext
---
|
|
title: Passkey
|
|
description: 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.
|
|
|
|
The passkey plugin implementation is powered by [SimpleWebAuthn](https://simplewebauthn.dev/) behind the scenes.
|
|
|
|
## Installation
|
|
|
|
<Steps>
|
|
<Step>
|
|
### Add the plugin to your auth config
|
|
To add the passkey plugin to your auth config, you need to import the plugin and pass it to the `plugins` option of the auth instance.
|
|
|
|
**Options**
|
|
|
|
`rpID`: A unique identifier for your website. 'localhost' is okay for local dev
|
|
|
|
`rpName`: Human-readable title for your website
|
|
|
|
`origin`: The URL at which registrations and authentications should occur. `http://localhost` and `http://localhost:PORT` are also valid. Do **NOT** include any trailing /
|
|
|
|
`authenticatorSelection`: Allows customization of WebAuthn authenticator selection criteria. Leave unspecified for default settings.
|
|
- `authenticatorAttachment`: Specifies the type of authenticator
|
|
- `platform`: Authenticator is attached to the platform (e.g., fingerprint reader)
|
|
- `cross-platform`: Authenticator is not attached to the platform (e.g., security key)
|
|
- Default: `not set` (both platform and cross-platform allowed, with platform preferred)
|
|
- `residentKey`: Determines credential storage behavior.
|
|
- `required`: User MUST store credentials on the authenticator (highest security)
|
|
- `preferred`: Encourages credential storage but not mandatory
|
|
- `discouraged`: No credential storage required (fastest experience)
|
|
- Default: `preferred`
|
|
- `userVerification`: Controls biometric/PIN verification during authentication:
|
|
- `required`: User MUST verify identity (highest security)
|
|
- `preferred`: Verification encouraged but not mandatory
|
|
- `discouraged`: No verification required (fastest experience)
|
|
- Default: `preferred`
|
|
|
|
|
|
```ts title="auth.ts"
|
|
import { betterAuth } from "better-auth"
|
|
import { passkey } from "better-auth/plugins/passkey" // [!code highlight]
|
|
|
|
export const auth = betterAuth({
|
|
plugins: [ // [!code highlight]
|
|
passkey(), // [!code highlight]
|
|
], // [!code highlight]
|
|
})
|
|
```
|
|
</Step>
|
|
<Step>
|
|
### Migrate the database
|
|
|
|
Run the migration or generate the schema to add the necessary fields and tables to the database.
|
|
|
|
<Tabs items={["migrate", "generate"]}>
|
|
<Tab value="migrate">
|
|
```bash
|
|
npx @better-auth/cli migrate
|
|
```
|
|
</Tab>
|
|
<Tab value="generate">
|
|
```bash
|
|
npx @better-auth/cli generate
|
|
```
|
|
</Tab>
|
|
</Tabs>
|
|
See the [Schema](#schema) section to add the fields manually.
|
|
</Step>
|
|
<Step>
|
|
### Add the client plugin
|
|
|
|
```ts title="auth-client.ts"
|
|
import { createAuthClient } from "better-auth/client"
|
|
import { passkeyClient } from "better-auth/client/plugins"
|
|
|
|
export const authClient = createAuthClient({
|
|
plugins: [ // [!code highlight]
|
|
passkeyClient() // [!code highlight]
|
|
] // [!code highlight]
|
|
})
|
|
```
|
|
</Step>
|
|
|
|
</Steps>
|
|
|
|
## Usage
|
|
|
|
### Add/Register a passkey
|
|
|
|
To add or register a passkey make sure a user is authenticated and then call the `passkey.addPasskey` function provided by the client.
|
|
|
|
```ts
|
|
// Default behavior allows both platform and cross-platform passkeys
|
|
const { data, error } = await authClient.passkey.addPasskey();
|
|
```
|
|
|
|
This will prompt the user to register a passkey. And it'll add the passkey to the user's account.
|
|
|
|
You can also specify the type of authenticator you want to register. The `authenticatorAttachment` can be either `platform` or `cross-platform`.
|
|
|
|
```ts
|
|
// Register a cross-platform passkey showing only a QR code
|
|
// for the user to scan as well as the option to plug in a security key
|
|
const { data, error } = await authClient.passkey.addPasskey({
|
|
authenticatorAttachment: 'cross-platform'
|
|
});
|
|
```
|
|
|
|
### Sign in with a passkey
|
|
|
|
To sign in with a passkey you can use the passkeySignIn method. This will prompt the user to sign in with their passkey.
|
|
|
|
Signin method accepts:
|
|
|
|
`autoFill`: Browser autofill, a.k.a. Conditional UI. [read more](https://simplewebauthn.dev/docs/packages/browser#browser-autofill-aka-conditional-ui)
|
|
|
|
`email`: The email of the user to sign in.
|
|
|
|
`fetchOptions`: Fetch options to pass to the fetch request.
|
|
|
|
```ts
|
|
const data = await authClient.signIn.passkey();
|
|
```
|
|
|
|
### Conditional UI
|
|
|
|
The plugin supports conditional UI, which allows the browser to autofill the passkey if the user has already registered a passkey.
|
|
|
|
There are two requirements for conditional UI to work:
|
|
|
|
<Steps>
|
|
<Step>
|
|
#### Update input fields
|
|
|
|
Add the `autocomplete` attribute with the value `webauthn` to your input fields. You can add this attribute to multiple input fields, but at least one is required for conditional UI to work.
|
|
|
|
The `webauthn` value should also be the last entry of the `autocomplete` attribute.
|
|
|
|
```html
|
|
<label for="name">Username:</label>
|
|
<input type="text" name="name" autocomplete="username webauthn">
|
|
<label for="password">Password:</label>
|
|
<input type="password" name="password" autocomplete="current-password webauthn">
|
|
```
|
|
</Step>
|
|
<Step>
|
|
#### Preload the passkeys
|
|
|
|
When your component mounts, you can preload the user's passkeys by calling the `authClient.signIn.passkey` method with the `autoFill` option set to `true`.
|
|
|
|
To prevent unnecessary calls, we will also add a check to see if the browser supports conditional UI.
|
|
|
|
<Tabs items={["React"]}>
|
|
<Tab value="React">
|
|
```ts
|
|
useEffect(() => {
|
|
if (!PublicKeyCredential.isConditionalMediationAvailable ||
|
|
!PublicKeyCredential.isConditionalMediationAvailable()) {
|
|
return;
|
|
}
|
|
|
|
void authClient.signIn.passkey({ autoFill: true })
|
|
}, [])
|
|
```
|
|
</Tab>
|
|
</Tabs>
|
|
|
|
</Step>
|
|
|
|
</Steps>
|
|
|
|
Depending on the browser, a prompt will appear to autofill the passkey. If the user has multiple passkeys, they can select the one they want to use.
|
|
|
|
Some browsers also require the user to first interact with the input field before the autofill prompt appears.
|
|
|
|
### Debugging
|
|
|
|
To test your passkey implementation you can use [emulated authenticators](https://developer.chrome.com/docs/devtools/webauthn). This way you can test the registration and sign-in process without even owning a physical device.
|
|
|
|
## Schema
|
|
|
|
The plugin require a new table in the database to store passkey data.
|
|
|
|
Table Name: `passkey`
|
|
|
|
<DatabaseTable
|
|
fields={[
|
|
{
|
|
name: "id",
|
|
type: "string",
|
|
description: "Unique identifier for each passkey",
|
|
isPrimaryKey: true
|
|
},
|
|
{
|
|
name: "name",
|
|
type: "string",
|
|
description: "The name of the passkey",
|
|
isOptional: true
|
|
},
|
|
{
|
|
name: "publicKey",
|
|
type: "string",
|
|
description: "The public key of the passkey",
|
|
},
|
|
{
|
|
name: "userId",
|
|
type: "string",
|
|
description: "The ID of the user",
|
|
isForeignKey: true
|
|
},
|
|
{
|
|
name: "credentialID",
|
|
type: "string",
|
|
description: "The unique identifier of the registered credential",
|
|
},
|
|
{
|
|
name: "counter",
|
|
type: "number",
|
|
description: "The counter of the passkey",
|
|
},
|
|
{
|
|
name: "deviceType",
|
|
type: "string",
|
|
description: "The type of device used to register the passkey",
|
|
},
|
|
{
|
|
name: "backedUp",
|
|
type: "boolean",
|
|
description: "Whether the passkey is backed up",
|
|
},
|
|
{
|
|
name: "transports",
|
|
type: "string",
|
|
description: "The transports used to register the passkey",
|
|
},
|
|
{
|
|
name: "createdAt",
|
|
type: "Date",
|
|
description: "The time when the passkey was created",
|
|
},
|
|
{
|
|
name: "aaguid",
|
|
type: "string",
|
|
description: "Authenticator's Attestation GUID indicating the type of the authenticator",
|
|
isOptional: true
|
|
},
|
|
]}
|
|
/>
|
|
|
|
## Options
|
|
|
|
**rpID**: A unique identifier for your website. 'localhost' is okay for local dev.
|
|
|
|
**rpName**: Human-readable title for your website.
|
|
|
|
**origin**: The URL at which registrations and authentications should occur. `http://localhost` and `http://localhost:PORT` are also valid. Do NOT include any trailing /.
|
|
|
|
**authenticatorSelection**: Allows customization of WebAuthn authenticator selection criteria. When unspecified, both platform and cross-platform authenticators are allowed with `preferred` settings for `residentKey` and `userVerification`.
|
|
|
|
**aaguid**: (optional) Authenticator Attestation GUID. This is a unique identifier for the passkey provider (device or authenticator type) and can be used to identify the type of passkey device used during registration or authentication. |