@basenative/auth-webauthn

中文文档
3.3.0发布于 2026/11/03Apache-2.0TypeScriptES Module零依赖构建来源已验证近 30 天下载 2135

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

中文使用文档
文档目录17 节
  1. 为什么
  2. 安装
  3. 快速启动( Cloudflares 页面函数+ D1)
  4. 1. 约束 wrangler.toml (中文(简体) )
  5. 2. 应用计划
  6. 3. 接通6名操作员
  7. 客户端使用量
  8. 角色种子
  9. 存储接口(合同)
  10. 配置
  11. userVerification
  12. 移动已存在的工程
  13. 从 express-session / cookie-session(待定)
  14. 从下Auth / Auth.js 发送
  15. 安全说明
  16. 测试
  17. 许可证

@auth-webauthn( 低调)

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

部分 基础 生态系统。 从经过生产测试的密码实施中取消 t4bs (单位:千美元)。 。 。 .

为什么

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

安装

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

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

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

1. 约束wrangler.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. 应用计划

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/它们都是一样的形状:

// 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),
  });
// functions/api/auth/register-options.js
import { registerOptionsHandler } from '@basenative/auth-webauthn/handlers';
import { getAdapter } from './_adapter.js';
export const onRequestPost = registerOptionsHandler(getAdapter);
// functions/api/auth/register-verify.js
import { registerVerifyHandler } from '@basenative/auth-webauthn/handlers';
import { getAdapter } from './_adapter.js';
export const onRequestPost = registerVerifyHandler(getAdapter);
// functions/api/auth/login-options.js
import { loginOptionsHandler } from '@basenative/auth-webauthn/handlers';
import { getAdapter } from './_adapter.js';
export const onRequestPost = loginOptionsHandler(getAdapter);
// 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',
    });
  },
});
// functions/api/auth/me.js
import { meHandler } from '@basenative/auth-webauthn/handlers';
import { getAdapter } from './_adapter.js';
export const onRequestGet = meHandler(getAdapter);
// functions/api/auth/logout.js
import { logoutHandler } from '@basenative/auth-webauthn/handlers';
import { getAdapter } from './_adapter.js';
export const onRequestPost = logoutHandler(getAdapter);

这是整个服务器侧.

客户端使用量

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();
}

自定义端点路径 :

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

角色种子

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

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 与这四个成员为对象:

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,如果您想要在程序上应用它.

配置

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' 当你的威胁模型需要第二个因素烘烤 (例如共享设备、高值账户): 更强,但它会拒绝任何认证者认证 无法执行紫外线,包括你打开前注册的证书 继续 只有在确认用户的验证器支持紫外线后,才能推出, 或与重新注册的瞬间.

移动已存在的工程

如果您的当前认证像 :

// 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)。如果你在快递上,请写一个薄的中间软件:
    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' 权衡取舍.

测试

pnpm --filter @basenative/auth-webauthn test

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

许可证

阿帕奇 - 2.0

版本与发布记录

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

版本发布日期距今
3.3.0latest2026/11/035 天前
3.2.02026/11/035 天前
3.1.02026/11/017 天前
3.0.02026/10/317 天前
2.4.02026/10/2613 天前
2.3.22026/10/2217 天前

关于 @basenative/auth-webauthn 的常见问题

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