Passkey 密钥
Passkey 密钥
Passkey 密钥是一种安全的无密码认证方法,使用加密密钥对,由 WebAuthn 和 FIDO2 标准支持,适用于网页浏览器。它用独特的密钥对替代密码:私钥存储在用户设备上,公钥共享给网站。用户可以通过生物识别、PIN 码或安全密钥登录,提供强大且抗钓鱼的认证,免除了传统密码的使用。
Passkey 插件的实现幕后由 SimpleWebAuthn 提供支持。
安装
安装插件
npm install @better-auth/passkey将插件添加到认证配置
要将 passkey 插件添加到您的认证配置中,需要导入插件并将其传递给认证实例的 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-first 引导流程或 WebAuthn 扩展。
import { betterAuth } from "better-auth"
import { passkey } from "@better-auth/passkey"
export const auth = betterAuth({
plugins: [
passkey({
registration: {
// 默认值:true。设置为 false 可用于 passkey-first 引导。
requireSession: false,
// 当 requireSession 为 false 且不存在会话时必需。
resolveUser: async ({ ctx, context }) => {
// 验证 context(例如签名令牌),然后创建或加载用户。
return { id: "user-id", name: "user@example.com" }
},
// 可选的服务端定义扩展
extensions: { credProps: true },
},
authentication: {
// 可选的服务端定义扩展
extensions: { credProps: true },
},
}),
],
})Passkey-first 注册(预认证)
当 registration.requireSession 为 false 时,可以在没有会话的情况下发起 passkey 注册。您可以向注册选项端点传递一个不透明的 context;它会被转发给 resolveUser。
await auth.api.generatePasskeyRegistrationOptions({
context: "signed-registration-token",
})当使用 passkey-first 流程(registration.requireSession: false)时,在注册 passkey 时,请从客户端传递相同的 context,以便服务器在验证期间解析用户:
await authClient.passkey.addPasskey({
name: "Primary passkey",
context: "signed-registration-token",
})用法
添加/注册 passkey 密钥
要添加或注册 passkey 密钥,请确保用户已认证,然后调用客户端提供的 passkey.addPasskey 函数。
const { data, error } = await authClient.passkey.addPasskey({ name: "example-passkey-name", authenticatorAttachment: "cross-platform", extensions, returnWebAuthnResponse, context,});namestring可选的名称,用于标记正在注册的认证器账户。如果未提供,默认将使用用户的电子邮件地址或用户 ID
authenticatorAttachment"platform" | "cross-platform"还可以指定要注册的认证器类型。默认行为允许注册平台密钥和跨平台密钥
extensionsAuthenticationExtensionsClientInputs可选的 WebAuthn 扩展(例如 PRF、credProps、largeBlob)
returnWebAuthnResponseboolean返回 WebAuthn 响应和扩展结果
contextstring用于 passkey-first 注册流程的可选上下文。转发到 registration.resolveUser。
在获取选项中设置 throw: true 对注册和登录的 passkey 响应没有影响——它们始终返回包含错误对象的数据对象。
使用 passkey 登录
要使用 passkey 登录,可以使用 signIn.passkey 方法。这会提示用户使用他们的 passkey 登录。
const { data, error } = await authClient.signIn.passkey({ autoFill: true, extensions, returnWebAuthnResponse,});autoFillboolean浏览器自动填充,也称为条件 UI。阅读更多:https://simplewebauthn.dev/docs/packages/browser#浏览器自动填充-aka-条件-ui
extensionsAuthenticationExtensionsClientInputs可选的 WebAuthn 扩展(例如 PRF、credProps、largeBlob)
returnWebAuthnResponseboolean返回 WebAuthn 响应和扩展结果
示例用法
import { authClient } from "@/lib/auth-client";
// 登录后跳转
await authClient.signIn.passkey({
autoFill: true,
// 可选扩展
extensions: { credProps: true },
fetchOptions: {
onSuccess(context) {
// 身份验证成功后跳转到仪表盘
window.location.href = "/dashboard";
},
onError(context) {
// 处理身份验证错误
console.error("身份验证失败:", context.error.message);
}
}
});扩展
您可以通过客户端 API 传递 extensions 来使用 WebAuthn 扩展。设置 returnWebAuthnResponse 为 true 时,客户端会返回 webauthn.clientExtensionResults。
const result = await authClient.passkey.addPasskey({
name: "My Passkey",
extensions: {
// 示例扩展输入(通用)
credProps: true,
},
returnWebAuthnResponse: true,
});
console.log(result.webauthn?.clientExtensionResults);列出 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。
namestringrequired更新后的新名称。
条件 UI
插件支持条件 UI,允许浏览器在用户已注册 passkey 时自动填充。
条件 UI 要正常工作需要两个条件:
更新输入字段
在输入字段中添加 autocomplete 属性,并将其值设置为 webauthn。您可以将此属性添加到多个输入字段,但至少需要一个才能使条件 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">预加载 passkey
当组件挂载时,您可以通过将 autoFill 选项设置为 true 来预加载用户的 passkey,调用 authClient.signIn.passkey 方法。
为了防止不必要的调用,我们还将添加检查以确认浏览器是否支持条件 UI。
useEffect(() => {
if (!PublicKeyCredential.isConditionalMediationAvailable ||
!PublicKeyCredential.isConditionalMediationAvailable()) {
return;
}
void authClient.signIn.passkey({ autoFill: true })
}, [])表名: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 文档。