# @basenative/auth-webauthn 中文文档

规范地址：https://npmcn.com/package/%40basenative/auth-webauthn/

版本：3\.3\.0
npm 元数据更新时间：2026-11-03T01:40:05.716Z
中文译文更新时间：2026-11-07T22:55:18.230Z
软件包许可：Apache-2\.0

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

原始 npm 页面：https://www.npmjs.com/package/%40basenative/auth-webauthn
原文对照：https://npmcn.com/package/%40basenative/auth-webauthn/?lang=original
项目主页：https://github.com/DuganLabs/BaseNative/tree/main/packages/auth-webauthn#readme
源码仓库：https://github.com/DuganLabs/BaseNative

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

- 包体积（解压后）：61.9 kB，15 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：0 个
- peer 依赖：@simplewebauthn/server ^11\.0\.0 \|\| ^12\.0\.0 \|\| ^13\.0\.0 \|\| ^14\.0\.0
- 首次发布：2026-10-18T05:28:58.680Z
- 最新版本发布：2026-11-03T01:40:05.368Z
- 近 30 天下载量：2135（2026-10-08 至 2026-11-06，npm 官方下载统计）
- 已发布版本数：14（近 12 个月 14 个）

## 中文简介

WebAuthn (passkey) 适配器, 用于 @ beasnative/ auth — 服务器、客户端和 Cloudflare Workers/ Pages 处理器

## 安装

```sh
npm install @basenative/auth-webauthn
```

也可以使用：`pnpm add @basenative/auth-webauthn`、`yarn add @basenative/auth-webauthn`、`bun add @basenative/auth-webauthn`

## README

# @auth-webauthn( 低调)

> Cloudflare Workers / Pages 的 WebAuthn (passkey) 供应商. 跟着一样 `{ type, ... }` 提供者形状为 `@basenative/auth`,但没有依赖它。 滴入密码登录,有可插件存储和微小浏览器客户端.

部分 [基础](https://basenative.dev/) 生态系统。 从经过生产测试的密码实施中取消 [t4bs (单位:千美元)](https://github.com/DuganLabs/t4bs)。 。 。 .

## 为什么

现代道具(英語:Modern passkey auth)是一款有不少相纸剪接器的少量密码. 这个软件包处理剪纸(挑战TTL, cookie旗, Base64url 编码, WebAuthn 版本漂移,角色种子),所以您的应用代码仍然小.

## 安装

```bash
pnpm add @basenative/auth-webauthn @simplewebauthn/server
# Browser-side (only on the client):
pnpm add @simplewebauthn/browser
```

`@simplewebauthn/server` 被装入懒惰的- 同伴在第一次使用前不会被拉入你的工人捆绑.

## 快速启动( Cloudflares 页面函数+ D1)

### 1\. 约束`wrangler.toml`(中文(简体) )

```toml
[[d1_databases]]
binding = "DB"
database_name = "myapp-db"
database_id = "..."

[vars]
RP_NAME   = "MyApp"
RP_ID     = "myapp.com"          # use "localhost" in dev
RP_ORIGIN = "https://myapp.com"  # use "http://localhost:8788" in dev
ADMIN_HANDLES = "warren,bob"     # optional — see "Role seeding"
```

### 2\. 应用计划

```bash
wrangler d1 execute DB --local  --file=./node_modules/@basenative/auth-webauthn/migrations/0001_webauthn_schema.sql
wrangler d1 execute DB --remote --file=./node_modules/@basenative/auth-webauthn/migrations/0001_webauthn_schema.sql
```

或者把SQL复制到你自己的 `migrations/` 目录.

### 3\. 接通6名操作员

每条路由创建一个文件 `functions/api/auth/`它们都是一样的形状:

```js
// functions/api/auth/_adapter.js
import {
  webauthnAdapter,
  d1WebAuthnStores,
} from '@basenative/auth-webauthn';

export const getAdapter = (env) =>
  webauthnAdapter({
    rp: { rpName: env.RP_NAME, rpID: env.RP_ID, origin: env.RP_ORIGIN },
    stores: d1WebAuthnStores(env.DB),
  });
```

```js
// functions/api/auth/register-options.js
import { registerOptionsHandler } from '@basenative/auth-webauthn/handlers';
import { getAdapter } from './_adapter.js';
export const onRequestPost = registerOptionsHandler(getAdapter);
```

```js
// functions/api/auth/register-verify.js
import { registerVerifyHandler } from '@basenative/auth-webauthn/handlers';
import { getAdapter } from './_adapter.js';
export const onRequestPost = registerVerifyHandler(getAdapter);
```

```js
// functions/api/auth/login-options.js
import { loginOptionsHandler } from '@basenative/auth-webauthn/handlers';
import { getAdapter } from './_adapter.js';
export const onRequestPost = loginOptionsHandler(getAdapter);
```

```js
// functions/api/auth/login-verify.js
import { loginVerifyHandler } from '@basenative/auth-webauthn/handlers';
import { seedRoles, parseHandleList, d1WebAuthnStores } from '@basenative/auth-webauthn';
import { getAdapter } from './_adapter.js';

export const onRequestPost = loginVerifyHandler(getAdapter, {
  onLogin: async ({ env, user }) => {
    if (!user) return;
    const stores = d1WebAuthnStores(env.DB);
    await seedRoles({
      stores,
      user,
      seedMap: parseHandleList(env.ADMIN_HANDLES, 'admin'),
      changedBy: 'seed:ADMIN_HANDLES',
    });
  },
});
```

```js
// functions/api/auth/me.js
import { meHandler } from '@basenative/auth-webauthn/handlers';
import { getAdapter } from './_adapter.js';
export const onRequestGet = meHandler(getAdapter);
```

```js
// functions/api/auth/logout.js
import { logoutHandler } from '@basenative/auth-webauthn/handlers';
import { getAdapter } from './_adapter.js';
export const onRequestPost = logoutHandler(getAdapter);
```

这是整个服务器侧.

## 客户端使用量

```js
import {
  isPasskeySupported,
  registerPasskey,
  loginPasskey,
  me,
  logout,
} from '@basenative/auth-webauthn/client';

if (isPasskeySupported()) {
  await registerPasskey('alice');   // → /me payload
  await loginPasskey('alice');      // or loginPasskey('') for usernameless
  const { user } = await me();
  await logout();
}
```

自定义端点路径 :

```js
await registerPasskey('alice', {
  paths: {
    registerOptions: '/auth/passkey/register/start',
    registerVerify:  '/auth/passkey/register/finish',
    me:              '/auth/whoami',
  },
});
```

## 角色种子

如果您的应用程序有管理员/ 调制解调器, 请将它们列在一个 env var( 或任何地方) 中 。 登录时, 自动升级匹配控件 :

```js
import { seedRoles, parseHandleList } from '@basenative/auth-webauthn';

await seedRoles({
  stores,
  user,
  seedMap: parseHandleList(env.ADMIN_HANDLES, 'admin'),
  changedBy: 'seed:ADMIN_HANDLES',
});
```

当用户已经处于或高于目标角色时——管理员不会被降级,这是不公开的. 使用 `seedMap` 直接用于任意绘图`{ alice: 'admin', mod-bob: 'moderator' }`) (中文(简体) ).

## 存储接口(合同)

`webauthnAdapter` 使用任意 `stores` 与这四个成员为对象:

```ts
interface WebAuthnStores {
  users: {
    getByHandle(handle): Promise<User | null>;
    getById(id):         Promise<User | null>;
    create({ id, handle }): Promise<User>;          // returns { id, handle, role: 'user' }
    setRole?(id, role, changedBy): Promise<void>;   // optional, used by seedRoles
  };

  credentials: {
    listByUser(userId):  Promise<Credential[]>;
    getById(credId):     Promise<Credential & { userId } | null>;
    create({ id, userId, publicKey, counter, transports }): Promise<void>;
    updateCounter(id, counter): Promise<void>;
  };

  challenges: {
    create({ challenge, userId, purpose, ttlSeconds }): Promise<void>;
    consume(challenge, purpose): Promise<{ userId } | null>; // single-use; deletes on read
  };

  userSessions: {
    create({ id, userId, ttlSeconds }): Promise<void>;
    getUser(token):       Promise<User | null>;     // returns null if expired
    destroy(token):       Promise<void>;
  };
}
```

`d1WebAuthnStores(DB)` 是引用执行; 将您自己写为 Postgres、 MySQL、 Redis 或 持久对象。 这个 `migration` 常数导出自 `@basenative/auth-webauthn/d1-stores` 包含与迁移文件相同的 SQL,如果您想要在程序上应用它.

## 配置

```js
webauthnAdapter({
  rp: {
    rpName: 'MyApp',
    rpID:   'myapp.com',
    origin: 'https://myapp.com',
  },
  stores,
  ttl: {
    sessionSeconds:   30 * 24 * 3600, // default
    challengeSeconds: 300,            // default; clamped to ≤ 300
  },
  cookieName:    'bn_auth',           // default
  secureCookie:  true,                // default; set false for localhost http://
  userVerification: 'preferred',      // default; also 'required' | 'discouraged'
});
```

会话饼干总是 `HttpOnly; SameSite=Lax`。 。 。 . `Secure` 默认状态.

### `userVerification`

控制仪式要求(和检查)用户核查的强烈程度—— PIN、生物鉴别技术或设备密码——而不只是用户 _存在_ (接触) 或水龙头。 一个选项驱动双方,让他们不能不同意:

-   登记: `authenticatorSelection.userVerification`
-   认证 : `userVerification`
-   核查: `requireUserVerification: userVerification === 'required'`

| 数值 | 仪式要求紫外线 | 核查强制紫外线 |
| --- | --- | --- |
| `'preferred'` (默认) | 是, 如果验证器支持它 | 没有 |
| `'required'` | 对 | **对** - 驳回没有紫外线的说法 |
| `'discouraged'` | 没有 | 没有 |

**安全说明:** `'preferred'` 是默认,因为它符合已有 行为和保持先前登记的每张密码——有些 认证器(特别是更古老的安全密钥)从不执行紫外线,只有用户 存在。 设定 `'required'` 当你的威胁模型需要第二个因素烘烤 (例如共享设备、高值账户): 更强,但它会拒绝任何认证者认证 无法执行紫外线,包括你打开前注册的证书 继续 只有在确认用户的验证器支持紫外线后,才能推出, 或与重新注册的瞬间.

## 移动已存在的工程

### 从 `express-session` / cookie-session(待定)

如果您的当前认证像 :

```js
// before
app.use(session({ secret, store: new RedisStore({ ... }) }));
app.post('/login', async (req, res) => {
  if (await checkPassword(req.body.email, req.body.password)) {
    req.session.user = await db.users.findByEmail(req.body.email);
    res.redirect('/');
  } else {
    res.status(401).send('bad-creds');
  }
});
app.get('/me', (req, res) => res.json({ user: req.session.user || null }));
```

地图是:

| 专会 | `@basenative/auth-webauthn` |
| --- | --- |
| `req.session.user` (由中件装入) | `await adapter.currentUser(request)` |
| `req.session.user = ...` (登录) | `verifyAuthentication` 返回会话符号 |
| `req.session.destroy()` (挂断) | `adapter.destroySession(token)` |
| `RedisStore` 编号 : `MemoryStore` | 互换 `stores.userSessions` 对于一个重新支持的 |
| `secret` (签名的饼干) | 不需要 — 会话符号不透明 + 随机 |

分步迁移:

1.  **入诸法相.** 运行 `migrations/0001_webauthn_schema.sql` 对你的DB。 如果你已经有一个 `users` 表格中,替换 `CREATE TABLE users` 与 `ALTER TABLE` 添加语句 `role`, (中文(简体) ), `role_changed_at`, (中文(简体) ), `role_changed_by`。保留您现有的 `email`, (中文(简体) ), `password_hash`等——仅用密码就不需要放下.
2.  **选一柄地.** 待办业务可能通过电子邮件进行认证;适配器根据 `^[a-z0-9_-]{2,24}$`。或者暴露一个单独的 `handle` 字段或覆盖 `validHandle` 写一个小的自定义存储层, 将邮件排出或规范化 。 (大多数应用程序在初始登录时会添加一次性"选择用户名"步骤;用户喜欢它比电子邮件更喜欢作为登录ID. )
3.  **平行运行两个认证 .** 上载新 `/api/auth/*` 与现有 `/login` 路线。 在账户设置页面上添加一个“ 设置密码” 按钮 `registerPasskey(currentUser.handle)`。现有的基于密码的登录继续工作.
4.  **移动会话读取 .** 在你身边 `req.session.user`切换到 `await adapter.currentUser(req)`。如果你在快递上,请写一个薄的中间软件:
    
    ```js
    app.use(async (req, _res, next) => {
      req.user = await adapter.currentUser(req);
      next();
    });
    ```
    
    适配器是运行时间不可知的——它只需要一个 `Request`\- 形状对象 `.headers.get('cookie')`. 包装快递 `req.headers` 如果需要的话.
5.  **切过.** 一旦每个活跃的用户至少有一个密码,就去掉密码端点. DB行可以留下来—— `password_hash` 成为无用.

### 从下Auth / Auth.js 发送

这些库已经讲了提供者插件;这个软件包符合同样的模式。 在自定义的下一条Auth提供者内执行适配器呼叫 `authorize` 打回那个包裹 `verifyAuthentication`中的饼干名称 `webauthnAdapter` 如果您想要复用会话, 应当匹配您的下一张Auth cookie, 否则使用不同的名称并调用两者 `currentUser` 财务报告和审定财务报表 `getSession` 直到你切过来.

## 安全说明

-   挑战行为单用并被删除 `consume()`,无论过期.
-   TTL的挑战被锁定到 5分钟的服务器侧面 不要试图超越它更高;通过更大值的消费者代码会默默地被夹住.
-   饼干是 `HttpOnly; Secure; SameSite=Lax`禁用 `Secure` 只用于本地- HTTP 开发 .
-   错误归来为 `{ error: 'reason' }` 具有数值状态。 原因字符串是稳定的,可以安全地日志;没有包含PII或PII可验证值.
-   `seedRoles` 只有升级。 它永远不会移除 `admin` 从用户处,即使种子列表中缺少它们——手动处理角色去除.
-   `userVerification` 默认为 `'preferred'` (紫外线请求,未执行)——见上文“构思”部分 `'required'` 权衡取舍.

## 测试

```bash
pnpm --filter @basenative/auth-webauthn test
```

单位测试注入了假 WebAuthn 原始对象(`opts.lib`因此,套房运行 没有 `@simplewebauthn/server` 已安装 。 生产代码从未通过 `opts.lib`;是内钩.

## 许可证

阿帕奇 - 2.0

## 常见问题

### @basenative/auth-webauthn 是什么？

WebAuthn \(passkey\) 适配器, 用于 @ beasnative/ auth — 服务器、客户端和 Cloudflare Workers/ Pages 处理器

### 如何安装 @basenative/auth-webauthn？

在项目目录运行 npm install @basenative/auth-webauthn，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：@simplewebauthn/server（^11\.0\.0 \|\| ^12\.0\.0 \|\| ^13\.0\.0 \|\| ^14\.0\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @basenative/auth-webauthn 支持 TypeScript 吗？

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

### @basenative/auth-webauthn 支持 ESM 还是 CommonJS？

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

### @basenative/auth-webauthn 可以商用吗？

@basenative/auth-webauthn 使用 Apache-2\.0 许可。宽松许可：允许商业使用、修改和再分发，并附带明确的专利授权；分发时需保留许可与 NOTICE 文件，修改过的文件要注明改动。以上为概括，不构成法律意见，请以许可原文为准。

### @basenative/auth-webauthn 的下载量有多少？

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

### @basenative/auth-webauthn 还在维护吗？

最新版本 3\.3\.0 发布于 2026/11/03（5 天前）；至今共发布 14 个版本，其中过去 12 个月发布了 14 个；首次发布于 2026/10/18。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

