# @activescott/auth-provider-passkey 中文文档

规范地址：https://npmcn.com/package/%40activescott/auth-provider-passkey/

版本：2\.3\.0
npm 元数据更新时间：2026-10-31T05:53:56.002Z
中文译文更新时间：2026-11-06T10:47:40.811Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40activescott/auth-provider-passkey
原文对照：https://npmcn.com/package/%40activescott/auth-provider-passkey/?lang=original
项目主页：https://github.com/activescott/auth#readme
源码仓库：https://github.com/activescott/auth

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：131 kB，63 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：3 个
- peer 依赖：@activescott/auth ^5\.0\.0
- 首次发布：2026-08-02T06:07:09.457Z
- 最新版本发布：2026-10-31T05:53:55.714Z
- 近 30 天下载量：3422（2026-10-05 至 2026-11-03，npm 官方下载统计）
- 已发布版本数：6（近 12 个月 6 个）

## 中文简介

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

## 安装

```sh
npm install @activescott/auth-provider-passkey
```

也可以使用：`pnpm add @activescott/auth-provider-passkey`、`yarn add @activescott/auth-provider-passkey`、`bun add @activescott/auth-provider-passkey`

## README

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

[![npm version](https://img.shields.io/npm/v/@activescott/auth-provider-passkey.svg)](https://www.npmjs.com/package/@activescott/auth-provider-passkey) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

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

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

## 使用量

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

```ts
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` 获取选项, 运行仪式, 并张贴结果 :

```ts
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` 将被捕获的错误排序为输入的结果, 这样您就可以显示自己的消息 :

```ts
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()` 使用“添加密码”按钮:

```tsx
// 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` 如果你改变这一点.
-   每次尝试失败后再次获取,因为每次挑战都是单用(参见 [挑战](https://npmcn.com/package/%40activescott/auth-provider-passkey/#readme-challenges))),并添加了密码后,因为下一个选项排除了它.
-   当一个在飞行中, 或当自动填充请求被绑定在选项上时, 从未开始第二个获取 。 每个选项响应都取代了挑战饼干,每个来源有一个.
-   当源代码上的其他任何选项获取选项时, 放弃其选项, 因为他们的挑战已经消失: 另一个准备呼叫, 另一个客户端, 或者另一个标签( told over) `BroadcastChannel`,或 `localStorage` ”)。 隐藏标签不刷新, 当显示其选项已丢失或已停止时会再次获取 .

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

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

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

```ts
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"`; 设定在生产中 |
| `rpID` | `appUrl` 主机名, 其他请求主机名 | 依赖方ID; 覆盖 `appUrl` (例如,母域) |
| `expectedOrigin` | `appUrl` 来源,其他请求来源 | 期望的 WebAuthn 来源,或其中的列表; 覆盖 `appUrl` |
| `challengeSecret` | (必须) | 在短命挑战饼干上签名 |
| `challengeExpiry` | `"5m"` | 挑战寿命 |
| `challengeCookieName` | `"auth_passkey_challenge"` | 挑战饼干名称 |

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

```ts
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 : 坚持未修改的元数据, 并如所储存的那样返回它—— 提供者用一个 [佐德语](https://zod.dev/) 每个读取和写回 批发通过 `IdentityStore.update` 每次签入后(计数+最后使用). 典型的身份表 :

```sql
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 :

```ts
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 是什么？

@ 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 和提交记录判断。

