面向 Node.js 的设备绑定会话凭证中间件,通过将 session cookie 绑定到设备端加密密钥来防止跨设备 cookie 重放攻击,并借助 Web Crypto polyfill 将防护覆盖到所有浏览器。
DBSC Toolkit
防止被盗的 session cookie 在其他设备上被重放。
面向 Node.js 的 Device Bound Session Credentials (DBSC) —— 一个与框架无关的核心库,提供针对 Express、Fastify、Hono、Next.js、NestJS、Koa、SvelteKit 和原生 node:http 的适配器,并附带 Web Crypto polyfill 以覆盖所有浏览器,而不仅仅是 Chrome。包含一个语言中立的协议规范和一个 Node 参考实现。
## 问题所在
攻击者窃取了 session cookie —— 通过 XSS、信息窃取恶意软件、日志泄露或被入侵的代理。一旦他们拿到了 cookie,就会将其粘贴到自己的浏览器中,从而冒充你的身份。`HttpOnly` 起不到作用。`Secure` 也无济于事。轮换 token 只能缩短风险窗口;它无法彻底关闭它。
## 解决方案
DBSC 在用户登录时,将会话与用户设备上生成的加密密钥绑定。cookie 仍然可能被盗 —— 但每次刷新以及每个受保护的请求,都需要该密钥的签名。另一台机器上的攻击者没有任何可以用来签名的东西。重放请求会收到 403 报错。
```
Browser Server
─────── ──────
sign in ─────────────────────────────▶ issue session + Secure-Session-Registration
generate keypair (TPM / IndexedDB)
register public key ──────────────────▶ store key, bind to session
◀────────────────── bound session cookie
...later, on a guarded request:
sign challenge with private key ──────▶ verify signature → 200
stolen cookie, no key ──────▶ 403 ✋
```
### 被盗的 cookie 能给攻击者带来什么
cookie 可以被顺利复制。但密钥不行 —— 它从未离开过用户的设备。因此,重放的请求无法通过证明校验。
## 在线演示
**
** —— 登录,然后点击 **"simulate stolen cookie"** 按钮。它会发送一个仅包含绑定 cookie 而没有证明的裸请求,并返回 `403 PROOF_MISSING`。这就是整个库浓缩在一个按钮中的功能。
Chromium 145+ 会进入 `tier: "dbsc"`(由 TPM 支持);Firefox/Safari 会进入 `tier: "bound"`(Web Crypto polyfill)。无论哪种方式,防护效果都是一样的。
## 为什么会有 DBSC Toolkit
原生 DBSC 目前仅在基于 Chromium 的浏览器上提供。这使得 Firefox、Safari、移动端以及旧版 Chromium 用户只能使用普通 cookie —— 如果你的目标是阻止整个用户群体的 cookie 窃取,这就失去了意义。
DBSC Toolkit 通过 Web Crypto polyfill 将设备绑定会话扩展到了这些浏览器上,因此每个用户都能获得相同的逐请求防护。目前,它是少数几个为 Node.js 提供跨浏览器 DBSC 风格保护的开源实现之一。
它分为两层构建。协议 —— 包括每个 header、JWS 结构以及存储和 cookie 契约 —— 作为**语言中立的规范**写在 [`spec/`](./spec/) 中,并带有真实的测试向量。这个 Node.js 包是该规范的**参考实现**。该规范的设计使得符合标准的服务器可以在任何生态系统中构建;未来自然会向 Python、PHP 和 Java/Keycloak 移植,而该规范及其[测试向量](./spec/vectors)正是它们的目标。目前尚无其他实现 —— Node.js 是当今唯一的实现。
## 安装
```
npm install dbsc-toolkit
```
框架和存储驱动是可选的 peer dependencies —— 只需安装你用到的部分:
```
npm install express ioredis # Express + Redis
npm install fastify @fastify/cookie pg # Fastify + Postgres
```
## 3 行代码集成
直接放入现有的 Express 应用中 —— 无需重写你的登录或会话存储:
```
const dbsc = createDbsc({ storage }); // 1. configure once
dbsc.install(app); // 2. mount protocol routes + SDK
await dbsc.bind(res, sessionId, { userId }); // 3. bind, in your login route
```
然后使用 `requireProof()` 保护敏感路由。完整全貌如下:
## 快速开始
```
import express from "express";
import { randomUUID } from "node:crypto";
import { createDbsc } from "dbsc-toolkit/express";
import { MemoryStorage } from "dbsc-toolkit/storage/memory";
const app = express();
app.use(express.json());
const dbsc = createDbsc({ storage: new MemoryStorage() }); // Redis/Postgres in prod
dbsc.install(app); // mounts protocol routes + SDK
app.post("/login", async (req, res) => {
await dbsc.bind(res, randomUUID(), { userId: req.body.username }); // the one new line
res.json({ ok: true });
});
app.post("/payment", express.raw({ type: "*/*" }), dbsc.requireProof(), payHandler);
```
在你的 HTML 中加载 polyfill,以便非 Chromium 浏览器能够达到 `tier: "bound"`:
```
```
完整的操作指南、故障模式和迁移时间表请见:**[docs/getting-started.md](./docs/getting-started.md)** 和 **[docs/integrating-existing-auth.md](./docs/integrating-existing-auth.md)**。
### 任意框架
该协议存在于一个与框架无关的核心库中 —— 纯函数加上一个 `StorageAdapter`,没有任何 HTTP 层的假设。这些适配器只是轻量级的封装;选择适合你的:
| 框架 | 导入路径 |
|---|---|
| Express | `dbsc-toolkit/express` |
| Fastify | `dbsc-toolkit/fastify` |
| Hono (Node, Bun, Deno, Workers) | `dbsc-toolkit/hono` |
| Next.js (App Router) | `dbsc-toolkit/nextjs` |
| NestJS | `dbsc-toolkit/nestjs` |
| Koa | `dbsc-toolkit/koa` |
| SvelteKit | `dbsc-toolkit/sveltekit` |
| 任何其他服务器 (原生 `node:http`) | `dbsc-toolkit/node` |
| [Better Auth](https://better-auth.com) | `@dbsc-toolkit/better-auth` |
**没有适合你框架的适配器?你依然可以使用它。** 每个适配器都是对同一个核心库的轻量封装,因此任何基于 Node 的 HTTP 框架都可以使用 —— 直接接入核心库即可:
```
import {
handleRegistration, handleRefresh,
handleBoundRegistration, handleBoundRefresh,
verifyBoundProof, // your requireProof() equivalent
issueChallenge, buildRegistrationHeader,
type StorageAdapter, // the only interface you implement
} from "dbsc-toolkit";
```
通用的 `dbsc-toolkit/node` 适配器在原生 `node:http` 之上所做的正是如此。完整示例:[docs/adapters.md](./docs/adapters.md#writing-your-own-adapter)。
## 对比分析
| | 普通 cookie | JWT (bearer) | 原生 DBSC (Chrome 145+) | **dbsc-toolkit** |
|---|:---:|:---:|:---:|:---:|
| 防重放(来自其他设备的被盗 cookie) | ❌ | ❌ | ✅ | ✅ |
| 适用于 Chrome / Edge / Brave | ✅ | ✅ | ✅ (TPM) | ✅ |
| 适用于 Firefox / Safari / 移动端 / 无 TPM | ✅ | ✅ | ❌ | ✅ (polyfill) |
| 针对 MITM 的逐请求 body-hash 证明 | ❌ | ❌ | ❌ | ✅ |
| 捕获证明的重放防御 | n/a | ❌ | n/a | ✅ (重放缓存) |
| Bearer/access-token 重放防御 (DPoP, RFC 9449) | n/a | ❌ | n/a | ✅ (`dbsc-toolkit/dpop`) |
| 多子域绑定 | 宽松 | 宽松 | ❌ | ✅ (`cookieScope: "site"`) |
| Better Auth 集成 | n/a | n/a | n/a | ✅ (插件) |
DBSC 是对你现有身份验证(密码、MFA、会话、JWT)的补充 —— 它填补了*签发后的重放漏洞*,这是其他方案都无法覆盖的盲区。更多内容:[docs/security/threat-model.md](./docs/security/threat-model.md)。
## 安全概览
已防御:
- ✅ 从其他设备重放被盗的 cookie
- ✅ 被盗的 bearer token(同属一类)
- ✅ XSS 读取 `document.cookie`
- ✅ 网络窃听 / TLS 剥离代理
- ✅ 服务器日志泄露
- ✅ MITM body 替换(已签名的 body 哈希)
未防御(请坦诚对待该边界):
- ⚠️ 读取浏览器配置文件的设备端恶意软件 —— 只有 `dbsc` 层级 (TPM) 才能防住;`bound` 层级的 polyfill 密钥仍存储在磁盘上
- ⚠️ 浏览器/操作系统被入侵,具有 `subtle.sign` 访问权限的恶意扩展
完整的 STRIDE 分析:[docs/security/threat-model.md](./docs/security/threat-model.md) · 最佳实践:[docs/security/best-practices.md](./docs/security/best-practices.md)。
## 防护层级
每个会话都带有一个 `tier`。你不需要直接对它进行控制 —— `requireProof()` 会执行强制校验 —— 但它告知你在特定浏览器上是如何实现绑定的。
`dbsc` 由硬件支持(TPM / Secure Enclave)。`bound` 是 Web Crypto polyfill(不可导出的 IndexedDB 密钥)。`none` 是未绑定或过期的会话。详细信息:[HOW-IT-WORKS.md](./HOW-IT-WORKS.md)。
polyfill 默认开启。如果只想运行原生 DBSC(Chromium 145+,无 `bound` 层级),请传入 `bound: false` —— 此时 `/dbsc-bound/*` 路由不会挂载,且 `requireProof()` 会放宽至原生绑定模式。
## 可选:针对 bearer token 的 DPoP (RFC 9449)
DBSC 绑定的是会话 **cookie**。如果你为 API 分发 **bearer / access token**,它们将面临与 cookie 相同的重放问题 —— 复制 `Authorization` header,即可在任何地方使用。DPoP 以相同的方式解决了这个问题:将 token 绑定到设备密钥,并要求每次请求提供 `DPoP` 证明。它作为独立的导入模块发布,脱离默认加载路径,因此不使用它的项目无需付出任何代价。
```
import { dpopConfirmation } from "dbsc-toolkit/dpop";
import { requireDpop } from "dbsc-toolkit/express";
// at issue time — bind the token to the device key:
const { jkt } = await dpopConfirmation(deviceJwk);
const token = signAccessToken({ sub, cnf: { jkt } }); // your JWT lib
// on the resource route — verify the proof and the binding:
app.get("/api/resource", requireDpop({ getBoundJkt }), handler);
```
`requireDpop` 从每个适配器导出(Express, Fastify, Hono, Next.js, NestJS, Koa, SvelteKit, `node:http`)。校验失败会返回 `401` 及 `WWW-Authenticate: DPoP`。请务必传入 `getBoundJkt`,以确保出示的 token 绑定到其密钥 —— 参见 [docs/dpop.md](./docs/dpop.md) 和 [spec/10-dpop.md](./spec/10-dpop.md)。
## 生态系统
| 包 | 内容说明 |
|---|---|
| `dbsc-toolkit` | 核心库 + 针对 Express、Fastify、Hono、Next.js、NestJS、Koa、SvelteKit 和原生 `node:http` 的适配器;支持 memory/Redis/Postgres 存储 |
| `dbsc-toolkit/dpop` | 可选的 DPoP (RFC 9449) —— 将 bearer/access token 绑定到设备密钥 |
| `dbsc-toolkit/client` | 浏览器 SDK + Web Crypto polyfill |
| [`@dbsc-toolkit/better-auth`](./packages/better-auth/) | 一流的 [Better Auth](https://better-auth.com) 插件 —— 自动绑定所有登录方式 |
```
// Better Auth — one plugin line, works on every framework:
import { dbsc } from "@dbsc-toolkit/better-auth" // auth.ts → plugins: [dbsc()]
```
## 协议与规范
通信协议作为语言中立的规范记录在 [`spec/`](./spec/) 中:包含 header 格式、JWS 和 JSON 结构、存储与 cookie 契约、错误代码以及真实的测试向量。`dbsc-toolkit` 是参考实现 —— 该规范使得符合标准的 DBSC 服务器能够使用任何语言构建,而不仅仅是 Node。
- [`spec/README.md`](./spec/README.md) —— 从这里开始
- [`spec/02-native-protocol.md`](./spec/02-native-protocol.md) —— 原生 Chromium 的通信流程
- [`spec/09-conformance.md`](./spec/09-conformance.md) —— “一致性”的含义及如何验证
## 路线图
- [x] Express, Fastify, Hono, Next.js 适配器
- [x] Memory, Redis, PostgreSQL 存储
- [x] Web Crypto polyfill (Firefox / Safari / 移动端 / 旧版 Chromium)
- [x] 逐请求证明 + body 签名 + 重放缓存
- [x] 多子域绑定 (`cookieScope: "site"`)
- [x] Better Auth 插件
- [x] NestJS, Koa, SvelteKit 和通用 `node:http` 适配器
- [x] 用于 bearer token 绑定的 DPoP (RFC 9449)
- [ ] Bun / Deno 原生路径
- [ ] 第三方安全审计
## 子路径导入
| 导入路径 | 内容说明 |
|---|---|
| `dbsc-toolkit` | 核心:类型、加密、协议处理器、与框架无关 |
| `dbsc-toolkit/express` · `/fastify` · `/hono` · `/nextjs` · `/nestjs` · `/koa` · `/sveltekit` · `/node` | 框架适配器 |
| `dbsc-toolkit/dpop` | 可选的 DPoP (RFC 9449) 验证器 + 每个适配器的 `requireDpop` |
| `dbsc-toolkit/client` | 浏览器 SDK + polyfill |
| `dbsc-toolkit/storage/{memory,redis,postgres}` | 存储适配器 |
## 文档
- **协议规范 (语言中立):** [spec/](./spec/) · [一致性测试](./spec/09-conformance.md) · [测试向量](./spec/vectors)
- **概念与协议:** [HOW-IT-WORKS.md](./HOW-IT-WORKS.md)
- **入门指南:** [docs/getting-started.md](./docs/getting-started.md)
- **添加到现有应用:** [docs/integrating-existing-auth.md](_URL_23/>)
- **逐请求签名与重放缓存:** [docs/per-request-signing.md](./docs/per-request-signing.md)
- **Bound polyfill 通信协议:** [docs/bound-polyfill.md](./docs/bound-polyfill.md)
- **API 参考:** [docs/api-reference.md](./docs/api-reference.md)
- **适配器(+ 构建你自己的):** [docs/adapters.md](./docs/adapters.md)
- **存储:** [docs/storage.md](./docs/storage.md) · **部署:** [docs/deployment.md](./docs/deployment.md)
- **安全:** [威胁模型](./docs/security/threat-model.md) · [最佳实践](./docs/security/best-practices.md)
- **故障排除:** [docs/troubleshooting.md](./docs/troubleshooting.md)
## 状态
已在 Chrome 147 / Windows / TPM 2.0 上完成端到端验证。原生 DBSC 需要在 Windows 或 Apple Silicon macOS 上运行 Chromium 145+;polyfill 覆盖了所有支持 Web Crypto + IndexedDB 的浏览器。目前尚未进行第三方安全审计 —— 参见 [HOW-IT-WORKS.md#production-readiness](./HOW-IT-WORKS.md#production-readiness)。
## 许可证
Apache 2.0