Passkey 密钥
Passkey 密钥
Passkey 密钥是一种安全的无密码认证方法,使用加密密钥对,由 WebAuthn 和 FIDO2 标准支持,适用于网页浏览器。它用独特的密钥对替代密码:私钥存储在用户设备上,公钥共享给网站。用户可以通过生物识别、PIN 码或安全密钥登录,提供强大且抗钓鱼的认证,免除了传统密码的使用。
Passkey 插件的实现幕后由 SimpleWebAuthn 提供支持。
安装
安装插件
npm install @better-auth/passkey将插件添加到身份验证配置中
要将 passkey 插件添加到您的身份验证配置中,需要导入该插件并传递给 auth 实例的 plugins 选项。
import { betterAuth } from "better-auth"
import { passkey } from "@better-auth/passkey"
export const auth = betterAuth({
plugins: [
passkey(),
],
})添加客户端插件
import { createAuthClient } from "better-auth/client"
import { passkeyClient } from "@better-auth/passkey/client"
export const authClient = createAuthClient({
plugins: [
passkeyClient()
]
})使用方法
添加/注册 passkey 密钥
要添加或注册 passkey 密钥,确保用户已认证,然后调用客户端提供的 passkey.addPasskey 函数。
const { data, error } = await authClient.passkey.addPasskey({ name: "example-passkey-name", authenticatorAttachment: "cross-platform",});namestring可选名称,用于标记正在注册的认证器账户。如果未提供,默认使用用户的邮件地址或用户 ID。
authenticatorAttachment"platform" | "cross-platform"你也可以指定要注册的认证器类型。默认行为允许平台和跨平台的 passkey。
在 fetch 选项中设置 throw: true 对注册和登录的 passkey 响应无效——它们始终返回包含错误对象的数据对象。
使用 passkey 登录
使用 passkey 登录可以调用 signIn.passkey 方法。这会提示用户使用其 passkey 登录。
const { data, error } = await authClient.signIn.passkey({ autoFill: true,});autoFillboolean浏览器自动填充,亦称为条件 UI。更多信息:https://simplewebauthn.dev/docs/packages/browser#browser-autofill-aka-conditional-ui
示例用法
import { authClient } from "@/lib/auth-client";
// 登录后跳转
await authClient.signIn.passkey({
autoFill: true,
fetchOptions: {
onSuccess(context) {
// 身份验证成功后跳转到仪表盘
window.location.href = "/dashboard";
},
onError(context) {
// 处理身份验证错误
console.error("身份验证失败:", context.error.message);
}
}
});列出 passkey
您可以通过调用 passkey.listUserPasskeys 列出所有已认证用户的 passkey:
const { data: passkeys, error } = await authClient.passkey.listUserPasskeys();删除 passkey
您可以通过调用 passkey.delete 并提供 passkey ID 来删除 passkey。
const { data, error } = await authClient.passkey.deletePasskey({ id: "some-passkey-id", // required});idstringrequired要删除的 passkey 的 ID。
更新 passkey 名称
const { data, error } = await authClient.passkey.updatePasskey({ id: "id of passkey", // required name: "my-new-passkey-name", // required});idstringrequired您想要更新的 passkey 的 ID。
namestringrequiredpasskey 将更新为的新名称。
条件 UI
插件支持条件 UI,允许浏览器在用户已注册 passkey 时自动填充。
条件 UI 要正常工作需要两个条件:
更新输入字段
为您的输入字段添加 autocomplete 属性,值为 webauthn。您可以为多个输入字段添加此属性,但至少一个是条件 UI 生效的前提。
webauthn 值应为 autocomplete 属性中的最后一项。
<label for="name">用户名:</label>
<input type="text" name="name" autocomplete="username webauthn">
<label for="password">密码:</label>
<input type="password" name="password" autocomplete="current-password webauthn">预加载 passkey
组件挂载时,可以通过调用 authClient.signIn.passkey 方法并设置 autoFill 选项为 true 来预加载用户的 passkey。
为避免不必要调用,我们还会检查浏览器是否支持条件 UI。
useEffect(() => {
if (!PublicKeyCredential.isConditionalMediationAvailable ||
!PublicKeyCredential.isConditionalMediationAvailable()) {
return;
}
void authClient.signIn.passkey({ autoFill: true })
}, [])根据浏览器不同,会出现提示以自动填充 passkey。如果用户有多个 passkey 可以选择想用的那个。
某些浏览器还需要用户先与输入框互动,提示才会弹出。
调试
测试 passkey 实现可以使用模拟认证器。这样您可以测试注册和登录流程,无需真实设备。
数据库架构
该插件需要在数据库中新建一张表来存储 passkey 数据。
表名:passkey
选项
rpID:基于您的身份验证服务器源的唯一网站标识符。开发环境可用 'localhost'。RP ID 可通过舍弃有效顶级域名左侧的零个或多个标签而形成,例如 www.example.com 可使用 www.example.com 或 example.com 作为 RP ID,但不能使用顶级域名 com。
rpName:您网站的人类可读名称。
origin:您的 better-auth 服务器所在的源 URL,例如 http://localhost 或 http://localhost:PORT。请勿包含尾部斜杠。
authenticatorSelection:允许自定义 WebAuthn 认证器选择标准。若不指定则为默认设置。
authenticatorAttachment:指定认证器类型platform:认证器连接于平台(例如指纹识别器)cross-platform:认证器非平台附加(例如安全密钥)- 默认:未设置(允许平台和跨平台,平台优先)
residentKey:决定凭据存储行为。required:用户必须将凭据存储于认证器(最高安全级别)preferred:鼓励存储凭据,但非强制discouraged:不要求存储凭据(最快体验)- 默认:
preferred
userVerification:控制认证时的生物识别 / PIN 验证:required:用户必须验证身份(最高安全)preferred:建议验证,但非强制discouraged:无需验证(最快体验)- 默认:
preferred
advanced:高级选项
webAuthnChallengeCookie:认证流程中存储 WebAuthn 挑战 ID 的 cookie 名称(默认:better-auth-passkey)
Expo 集成
使用 passkey 插件和 Expo 时,需要在 Expo 客户端配置 cookiePrefix 选项,以确保正确检测和存储 passkey cookies。
默认情况下,passkey 插件使用 "better-auth-passkey" 作为挑战 cookie 名称。该名称以 "better-auth" 开头,因此适用于 Expo 客户端默认配置。但如果自定义了 webAuthnChallengeCookie,则必须同步更新 Expo 客户端配置中的 cookiePrefix。
示例配置
如果你使用了自定义 cookie 名称:
import { betterAuth } from "better-auth";
import { passkey } from "@better-auth/passkey";
export const auth = betterAuth({
plugins: [
passkey({
advanced: {
webAuthnChallengeCookie: "my-app-passkey" // 自定义 cookie 名称
}
})
]
});确保在 Expo 客户端配置中匹配该前缀:
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" // 必须与自定义 cookie 名称的前缀匹配
}),
passkeyClient()
]
});如果使用多种认证系统或自定义多个 cookie 名称,也可以传递前缀数组:
expoClient({
storage: SecureStore,
cookiePrefix: ["better-auth", "my-app", "custom-auth"]
})如果 cookiePrefix 与您的 webAuthnChallengeCookie 的前缀不匹配,passkey 认证流程将会失败,因为挑战 cookie 无法存储并在验证时发送回服务器。
欲了解更多 Expo 集成信息,请参见 Expo 文档。