@activescott/auth-provider-passkey

中文文档
2.3.0发布于 2026/10/31MITTypeScriptES Module构建来源已验证近 30 天下载 3422

@ actionscott/ auth 的 Passkey (WebAuthn) 提供者

中文使用文档
文档目录7 节
  1. 使用量
  2. 在水龙头前获取选项
  3. 终点
  4. 配置
  5. 存储:密码是身份
  6. 挑战
  7. 跨平台注释

@ activescott/auth-professer-passkey (英语)

npm version License: MIT

密码( WebAuthn) 提供者 @activescott/auth。用户在签名时添加了密码(先通过电子邮件或短信),然后在无用户名中签名,包括Touch ID、Face ID、Windows Hello、Android、1Password、iCloud 键链或安全键.

服务器侧WebAuthn 验证用途 @simplewebauthn/server (网易财经. 零依赖性浏览器客户端船作为 @activescott/auth-provider-passkey/browser 子路径导出 .

使用量

服务器接线 - 没有新的存储接口; 密码重用 IdentityStore 你已经有了:

import { Auth, InMemoryChallengeStore } from "@activescott/auth"
import { PasskeyProvider } from "@activescott/auth-provider-passkey"

const auth = new Auth({
  session: { secret: process.env.JWT_SECRET! /* ... */ },
  userStore,
  identityStore,
  challengeStore: new InMemoryChallengeStore(), // DB-backed in production
  providers: [
    // other providers such as email and/or SMS go here too — users add
    // a passkey while signed in, so another provider handles first sign-in
    new PasskeyProvider({
      rpName: "MyApp",
      // Bind passkeys to your canonical domain in production; leave unset in
      // dev so rpID and origin derive from each request (e.g. localhost).
      appUrl:
        process.env.NODE_ENV === "production" ? process.env.APP_URL : undefined,
      challengeSecret: process.env.JWT_SECRET!,
    }),
  ],
})

浏览器(所有四个端点都是取出/JSON-WebAuthn仪式在页面JavaScript中运行,而不是形成导航). createPasskeyClient 获取选项, 运行仪式, 并张贴结果 :

import { createPasskeyClient } from "@activescott/auth-provider-passkey/browser"

const passkeys = createPasskeyClient() // basePath defaults to "/auth"

// Add a passkey (user must be signed in):
await passkeys.registerPasskey()

// Sign in with a passkey:
await passkeys.signInWithPasskey()
location.assign("/dashboard") // session cookie is set

当用户取消或服务器拒绝请求时,两者都会抛出. 服务器拒绝 Error.message 是最具体的文本 details.reason (例如:. "Unknown credential",在密码管理器中保存的密码密钥,其服务器的身份已不复存在),否则其密码密钥 message 因此,显示用户是合适的.

当浏览器的密码提示结束时没有证书(用户取消了它,因为它的站点没有这个设备的密码,或者另一个仪式中止了它),浏览器扔出一个 NotAllowedError, (中文(简体) ), AbortError,或 TimeoutError 他们的文字读起来像一个错误. describePasskeyError 将被捕获的错误排序为输入的结果, 这样您就可以显示自己的消息 :

import {
  createPasskeyClient,
  describePasskeyError,
} from "@activescott/auth-provider-passkey/browser"

try {
  await passkeys.signInWithPasskey()
} catch (caught) {
  const failure = describePasskeyError(caught)
  // { ok: false, reason: "no-credential-or-cancelled" | "error", message, error }
  showError(
    failure.reason === "no-credential-or-cancelled"
      ? "No passkey for this site yet. Sign in with email, then add one."
      : failure.message,
  )
}

浏览器刻意不说其中的哪个发生了,因此一个站点无法探测出密码;一个信息必须覆盖所有密码.

在水龙头前获取选项

Safari(iOS和macOS)只显示密码板,而索要的窃听器仍在处理中。 独自召唤, signInWithPasskey() 先获取选项, 然后再打电话 navigator.credentials.get,而Safari 失败与 NotAllowedError调用 prepareSignIn() 当报应者升起的时候 prepareRegistration() 使用“添加密码”按钮:

// Create the client once, at module scope
export const passkeys = createPasskeyClient()

// In the component that renders the button; the returned function is the cleanup
useEffect(() => passkeys.prepareSignIn(), [])

选择一到,水龙头就到 navigator.credentials.get (或 create) 之前 signInWithPasskey() (或 registerPasskey())返回。 在准备时,客户端:

  • 在挑战到期前再次获取 optionsMaxAge 毫秒( 毫秒)createPasskeyClient({ optionsMaxAge }),默认为4分钟,默认为1分钟 challengeExpiry) (中文(简体) ). 装到下面 challengeExpiry 如果你改变这一点.
  • 每次尝试失败后再次获取,因为每次挑战都是单用(参见 挑战)),并添加了密码后,因为下一个选项排除了它.
  • 当一个在飞行中, 或当自动填充请求被绑定在选项上时, 从未开始第二个获取 。 每个选项响应都取代了挑战饼干,每个来源有一个.
  • 当源代码上的其他任何选项获取选项时, 放弃其选项, 因为他们的挑战已经消失: 另一个准备呼叫, 另一个客户端, 或者另一个标签( told over) BroadcastChannel,或 localStorage ”)。 隐藏标签不刷新, 当显示其选项已丢失或已停止时会再次获取 .

水龙头仍在等待取来, Safari 拒绝该时间的表, 当它出现在第一个选项到达之前, 在刷新的时候, 在有别的东西取代了饼干之后, 或者在标签被隐藏之后, 或者在后向缓存过去 optionsMaxAge 和在大地之前, 自动填充请求待决后, 选项不会被刷新, 因此在后方有一个自带 optionsMaxAge 也等着 两种都保持可见的标签,如两个相并的窗口,都无法同时备妥:最后取取的是一个. 客户端没有准备的信号可以禁用按钮 。 经过一次拒绝的敲击后,它再次取出,而下一次的敲击一旦落地就会起作用.

没有准备的号召,这两种方法仍然有效。 他们在水龙头取水, Chrome, Firefox,和Edge允许.

对于有条件的UI(登录表单上的密码自动填充),添加 autocomplete="username webauthn" 到您的用户名/ 电子邮件输入, 并在页面加载时启动有条件的请求 :

import {
  createPasskeyClient,
  isConditionalUIAvailable,
} from "@activescott/auth-provider-passkey/browser"

if (await isConditionalUIAvailable()) {
  // Resolves once the user picks a passkey from the autofill suggestions and
  // the server has set the session cookie. A later signInWithPasskey() call
  // (e.g. from a button) aborts this pending request.
  await createPasskeyClient().signInWithPasskey({ conditional: true })
  location.assign("/dashboard")
}

startRegistration 财务报告和审定财务报表 startAuthentication 仍然为运行 HTTP 圆通路本身的代码导出,但会为客户端而贬值.

终点

终点需要认证目的
POST /auth/passkey/register-options第二届会议为登录用户添加密码的注册选项
POST /auth/passkey/register-verify第二届会议校验证明, 保存证书, 链接密码身份
POST /auth/passkey/authenticate-options无认证选项( 空) allowCredentials • 任何可发现的证书)
POST /auth/passkey/authenticate-verify无校验断言并设定会话 cookie

登记模式: 自动加码。用户先与另一个提供者(电子邮件,短信)签名,然后从设置/dashboard页面中添加一个密码;之后他们可以用无用户名签名。 不支持 Passkey- first 注册 .

配置

选项默认说明
rpName(必须)认证符提示中显示的依赖方名称
appUrl(未设定)Canonical app URL(例如:. "https://myapp.example"; 设定在生产中
rpIDappUrl 主机名, 其他请求主机名依赖方ID; 覆盖 appUrl (例如,母域)
expectedOriginappUrl 来源,其他请求来源期望的 WebAuthn 来源,或其中的列表; 覆盖 appUrl
challengeSecret(必须)在短命挑战饼干上签名
challengeExpiry"5m"挑战寿命
challengeCookieName"auth_passkey_challenge"挑战饼干名称

要接受几个共享一个依赖方ID主机的密码,例如网站及其预览子域,通过一个列表. 密钥会绑住 rpID,所以一个保存在 example.com 他们都工作:

new PasskeyProvider({
  rpName: "MyApp",
  appUrl: "https://example.com", // rpID "example.com"
  expectedOrigin: ["https://example.com", "https://beta.example.com"],
  challengeSecret: process.env.JWT_SECRET!,
})

需要列表 rpID 或者说 appUrl 设置。 除非每个 http(s) 来源是赤出源( 无路径或后端斜线) , 否则构造者会丢入 rpID 或它的子域,因为浏览器拒绝其它任何地方的仪式. Android等非网络起源 android:apk-key-hash:... 被无节制地通过.

存储:密码是身份

每个密码是普通的身份行—— {provider: "passkey", identifier: <base64url credential ID>} -所以你现在.. IdentityStore 这是唯一的存储。 证书的验证状态(公用钥匙,签名柜台,运输,设备类型,.) 活在行提供商拥有的 Identity.metadata您的商店将元数据视为不透明 JSON blob : 坚持未修改的元数据, 并如所储存的那样返回它—— 提供者用一个 佐德语 每个读取和写回 批发通过 IdentityStore.update 每次签入后(计数+最后使用). 典型的身份表 :

CREATE TABLE identities (
  id          TEXT PRIMARY KEY,
  user_id     TEXT NOT NULL REFERENCES users (id),
  provider    TEXT NOT NULL,       -- 'email' | 'sms' | 'passkey' | ...
  identifier  TEXT NOT NULL,       -- email, E.164 phone, or WebAuthn credential ID
  metadata    JSONB NOT NULL DEFAULT '{}', -- provider-owned; opaque to the app
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  verified_at TIMESTAMPTZ,
  UNIQUE (provider, identifier)
);
CREATE INDEX identities_user_id ON identities (user_id);

元数据可能包含敏感材料——把它当作证书数据处理(休息时加密是一种合理的默认). 这里的廉洁比保密更重要:任何能写出这个一栏的人都可以自己注册自己的密钥,因此卫视也相应写出.

要列出用户设置页面的密码, listPasskeys 过滤其身份以获取密码, 验证每行的提供者状态( 抽取无效行) , 并返回您可以直接转到页面的普通 JSON :

import { listPasskeys } from "@activescott/auth-provider-passkey"

const passkeys = await listPasskeys(identityStore, user.id)
// [{ credentialId, nickname: string | null, synced: boolean,
//    createdAt: ISO string, lastUsedAt: ISO string | null }, ...]

标记是你的; synced 对于同步到云密钥链或密码管理器的密码.

挑战

选项端点设置了 HttpOnly, SameSite=Lax cookie , 包含已签名的 JWT(challengeSecret,5分钟过期),将仪式与浏览器绑定,并将挑战记录在核心中 challengeStore核查终点既需要,也需要消耗储存在第一次救赎努力上的挑战——成功与否——因此,每项挑战严格来说都是单用.

prepareSignIn() 财务报告和审定财务报表 prepareRegistration() 在打开一页时,每隔几分钟获取一个选项,而这些挑战中的大多数永远得不到弥补. InMemoryChallengeStore 扫描已过期行; a ChallengeStore 您必须删除过去的行 expiresAt 自己来.

跨平台注释

  • 同步的密码 (iCloud Keychain, Google 密码管理器, 1Password) 报告 deviceType: "multiDevice" 和通常为0的签名柜台. 反回归被记录为警告,但确实 没有 失败认证——同步的密码折回计数器合法,因此屏蔽会锁住真正的用户.
  • 参数定义: 密码与依赖方ID绑定. localhost (a) 必须在生产领域建立生产钥匙。 rpID的子域可以使用证书;不同的可注册域不能使用.
  • 认证器是用户的选择: 以 residentKey: "preferred", (中文(简体) ), userVerification: "preferred",没有 authenticatorAttachment,所以平台验证器,密码管理器, 和漫游安全密钥都工作.
  • 算法:ES256和RS256被接受,涵盖苹果,Google,微软,以及共同的安全密钥.

版本与发布记录

@activescott/auth-provider-passkey 共发布 6 个版本,首次发布于 2026/08/02;过去 12 个月发布了 6 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.3.0latest2026/10/317 天前
2.2.02026/10/031 个月前
2.1.02026/09/191 个月前
2.0.02026/08/122 个月前
1.0.02026/08/092 个月前
0.1.02026/08/023 个月前

关于 @activescott/auth-provider-passkey 的常见问题

@activescott/auth-provider-passkey 是什么?

@ actionscott/ auth 的 Passkey (WebAuthn) 提供者

如何安装 @activescott/auth-provider-passkey?

在项目目录运行 npm install @activescott/auth-provider-passkey,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@activescott/auth(^5.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@activescott/auth-provider-passkey 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@activescott/auth-provider-passkey 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@activescott/auth-provider-passkey 可以商用吗?

@activescott/auth-provider-passkey 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@activescott/auth-provider-passkey 的下载量有多少?

根据 npm 官方下载统计,@activescott/auth-provider-passkey 在 2026/10/05 – 2026/11/03 这 30 天内被下载 3,422 次,日均约 114 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@activescott/auth-provider-passkey 还在维护吗?

最新版本 2.3.0 发布于 2026/10/31(7 天前);至今共发布 6 个版本,其中过去 12 个月发布了 6 个;首次发布于 2026/08/02。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。