密码钥匙
通行密钥是一种安全的无密码认证方法,使用加密密钥对,并由网页浏览器支持的 WebAuthn 和 FIDO2 标准。它们用唯一的密钥对取代密码:私钥存储在用户设备上,公钥与网站共享。用户可以使用生物识别、PIN 码或安全密钥登录,从而提供强大且抗网络钓鱼的认证,而无需传统密码。
【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.】
这个通行密钥插件的实现背后由 SimpleWebAuthn 提供支持。
【The passkey plugin implementation is powered by SimpleWebAuthn behind the scenes.】
安装
【Installation】
安装插件
npm install @better-auth/passkeyAdd 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.
import { betterAuth } from "better-auth"
import { passkey } from "@better-auth/passkey"
export const auth = betterAuth({
plugins: [
passkey(),
],
})Migrate the database
运行迁移或生成架构,以向数据库添加必要的字段和表。
npx @better-auth/cli migratenpx @better-auth/cli generateSee the Schema section to add the fields manually.
Add the client plugin
import { createAuthClient } from "better-auth/client"
import { passkeyClient } from "@better-auth/passkey/client"
export const authClient = createAuthClient({
plugins: [
passkeyClient()
]
})用法
【Usage】
添加/注册通行密钥
【Add/Register a passkey】
要添加或注册通行密钥,请确保用户已通过身份验证,然后调用客户端提供的 passkey.addPasskey 函数。
【To add or register a passkey make sure a user is authenticated and then call the passkey.addPasskey function provided by the client.】
const { data, error } = await authClient.passkey.addPasskey({ name: "example-passkey-name", authenticatorAttachment: "cross-platform",});| Prop | Description | Type |
|---|---|---|
name? | An optional name to label the authenticator account being registered. If not provided, it will default to the user's email address or user ID | string |
authenticatorAttachment? | You can also specify the type of authenticator you want to register. Default behavior allows both platform and cross-platform passkeys | "platform" | "cross-platform" |
在获取选项中设置 throw: true 对注册和登录的通行密钥响应没有效果 - 它们始终会返回包含错误对象的数据对象。
使用通行密钥登录
【Sign in with a passkey】
要使用通行密钥登录,你可以使用 signIn.passkey 方法。这将提示用户使用他们的通行密钥登录。
【To sign in with a passkey you can use the signIn.passkey method. This will prompt the user to sign in with their passkey.】
const { data, error } = await authClient.signIn.passkey({ autoFill: true,});| Prop | Description | Type |
|---|---|---|
autoFill? | Browser autofill, a.k.a. Conditional UI. Read more: https://simplewebauthn.dev/docs/packages/browser#browser-autofill-aka-conditional-ui | boolean |
示例用法
【Example Usage】
// With post authentication redirect
await authClient.signIn.passkey({
autoFill: true,
fetchOptions: {
onSuccess(context) {
// Redirect to dashboard after successful authentication
window.location.href = "/dashboard";
},
onError(context) {
// Handle authentication errors
console.error("Authentication failed:", context.error.message);
}
}
});列出密钥
【List passkeys】
你可以通过调用 passkey.listUserPasskeys 列出已验证用户的所有通行密钥:
【You can list all of the passkeys for the authenticated user by calling passkey.listUserPasskeys:】
const { data: passkeys, error } = await authClient.passkey.listUserPasskeys();删除通行密钥
【Deleting passkeys】
你可以通过调用 passkey.delete 并提供通行密钥 ID 来删除通行密钥。
【You can delete a passkey by calling passkey.delete and providing the passkey ID.】
const { data, error } = await authClient.passkey.deletePasskey({ id: "some-passkey-id", // required});| Prop | Description | Type |
|---|---|---|
id | The ID of the passkey to delete. | string |
更新通行密钥名称
【Updating passkey names】
const { data, error } = await authClient.passkey.updatePasskey({ id: "id of passkey", // required name: "my-new-passkey-name", // required});| Prop | Description | Type |
|---|---|---|
id | The ID of the passkey which you want to update. | string |
name | The new name which the passkey will be updated to. | string |
条件式界面
【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:】
更新输入字段
在你的输入字段中添加值为 webauthn 的 autocomplete 属性。你可以将此属性添加到多个输入字段,但至少需要一个输入字段以确保条件 UI 正常工作。
webauthn 的值也应该是 autocomplete 属性的最后一项。
<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">Preload the passkeys
当你的组件挂载时,你可以通过调用 authClient.signIn.passkey 方法并将 autoFill 选项设置为 true 来预加载用户的通行密钥。
为了防止不必要的调用,我们还将添加一个检查,以查看浏览器是否支持条件用户界面。
useEffect(() => {
if (!PublicKeyCredential.isConditionalMediationAvailable ||
!PublicKeyCredential.isConditionalMediationAvailable()) {
return;
}
void authClient.signIn.passkey({ autoFill: true })
}, [])根据浏览器的不同,会出现提示以自动填写密钥。如果用户有多个密钥,他们可以选择想要使用的那一个。
【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. 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.】
表名:passkey
【Table Name: passkey】
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | Unique identifier for each passkey | |
| name | string | The name of the passkey | |
| publicKey | string | - | The public key of the passkey |
| userId | string | The ID of the user | |
| credentialID | string | - | The unique identifier of the registered credential |
| counter | number | - | The counter of the passkey |
| deviceType | string | - | The type of device used to register the passkey |
| backedUp | boolean | - | Whether the passkey is backed up |
| transports | string | The transports used to register the passkey | |
| createdAt | Date | The time when the passkey was created | |
| aaguid | string | Authenticator's Attestation GUID indicating the type of the authenticator |
选项
【Options】
rpID:基于你的认证服务器来源,为你的网站设置的唯一标识符。'localhost' 在本地开发环境下是可以使用的。RP ID 可以通过丢弃其有效域名最左侧的零个或多个标签来形成,直到达到有效顶层域名 (TLD)。因此,www.example.com 可以使用 RP ID www.example.com 或 example.com,但不能使用 com,因为那是一个顶层域名 (eTLD)。
rpName:你网站的人类可读标题。
origin: 托管你更好认证服务器的源 URL。http://localhost 和 http://localhost:端口 也是有效的。不要包含任何结尾的 /。
authenticatorSelection:允许自定义 WebAuthn 认证器的选择标准。若不指定,将使用默认设置。
authenticatorAttachment:指定身份验证器的类型platform:认证器附加在平台上(例如,指纹识别器)cross-platform:身份验证器未附加到平台(例如,安全密钥)- 默认:
not set(允许平台和跨平台,两者皆可,但优先使用平台)
residentKey:决定凭证存储行为。required:用户必须在身份验证器上存储凭据(最高安全性)preferred:鼓励存储凭证,但不是强制性的discouraged:无需存储凭证(体验最快)- 默认:
preferred
userVerification:在身份验证过程中控制生物识别/密码验证:required:用户必须验证身份(最高安全级别)preferred:建议验证,但非强制discouraged:无需验证(体验最快)- 默认:
preferred
高级:高级选项
webAuthnChallengeCookie:用于在身份验证流程中存储 WebAuthn 挑战 ID 的 Cookie 名称(默认值:better-auth-passkey)
Expo 集成
【Expo Integration】
在使用 Expo 的 passkey 插件时,你需要在 Expo 客户端中配置 cookiePrefix 选项,以确保 passkey cookie 能被正确检测和存储。
【When using the passkey plugin with Expo, you need to configure the cookiePrefix option in the Expo client to ensure passkey cookies are properly detected and stored.】
默认情况下,通行密钥插件使用“better-auth-passkey”作为挑战 Cookie 的名称。由于这以“better-auth”开头,它能用默认的 Expo 客户端配置。不过,如果你自定义了“webAuthnChallengeCookie”选项,也必须在 Expo 客户端配置中更新“cookiePrefix”。
【By default, the passkey plugin uses "better-auth-passkey" as the challenge cookie name. Since this starts with "better-auth", it will work with the default Expo client configuration. However, if you customize the webAuthnChallengeCookie option, you must also update the cookiePrefix in your Expo client configuration.】
示例配置
【Example Configuration】
如果你使用自定义的 Cookie 名称:
【If you're using a custom cookie name:】
import { betterAuth } from "better-auth";
import { passkey } from "@better-auth/passkey";
export const auth = betterAuth({
plugins: [
passkey({
advanced: {
webAuthnChallengeCookie: "my-app-passkey" // Custom cookie name
}
})
]
});请确保将你的 Expo 客户端配置为使用匹配的前缀:
【Make sure to configure your Expo client with the matching prefix:】
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" // Must match the prefix of your custom cookie name
}),
passkeyClient()
]
});如果你使用多个身份验证系统或自定义 cookie 名称,你可以提供一个前缀数组:
【If you're using multiple authentication systems or custom cookie names, you can provide an array of prefixes:】
expoClient({
storage: SecureStore,
cookiePrefix: ["better-auth", "my-app", "custom-auth"]
})如果 cookiePrefix 与你的 webAuthnChallengeCookie 前缀不匹配,密码钥认证流程将会失败,因为在验证过程中,挑战 cookie 不会被存储并发送回服务器。
有关 Expo 集成的更多信息,请参阅 Expo 文档。
【For more information on Expo integration, see the Expo documentation.】