dbsc-toolkit 2.9.1 → 2.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,18 +7,48 @@
|
|
|
7
7
|
|
|
8
8
|
> **Stop stolen session cookies from being replayed on another device.**
|
|
9
9
|
>
|
|
10
|
-
> `dbsc-toolkit` implements W3C [Device Bound Session Credentials](https://www.w3.org/TR/dbsc/) on the server, and ships a Web Crypto polyfill so the same protection works on browsers that don't speak native DBSC yet. Native DBSC on Chromium 146+
|
|
10
|
+
> `dbsc-toolkit` implements W3C [Device Bound Session Credentials](https://www.w3.org/TR/dbsc/) on the server, and ships a Web Crypto polyfill so the same protection works on browsers that don't speak native DBSC yet. Native DBSC on Chromium 146+ (TPM / Secure Enclave); the polyfill on every other browser. **One server, every browser, no user prompts.**
|
|
11
11
|
>
|
|
12
12
|
> Works with **Express · Fastify · Hono · Next.js · Redis · PostgreSQL**.
|
|
13
13
|
|
|
14
|
+
```ts
|
|
15
|
+
// In your login route — one line to bind the session:
|
|
16
|
+
app.post("/login", async (req, res) => {
|
|
17
|
+
await dbsc.bind(res, sessionId, { userId });
|
|
18
|
+
res.json({ ok: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// On any sensitive route — one guard:
|
|
22
|
+
app.post("/payment", express.raw({ type: "*/*" }), requireProof(), paymentHandler);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
That's it. The rest — key generation, protocol routes, cookie management, polyfill — is handled by the library.
|
|
26
|
+
|
|
27
|
+
## Contents
|
|
28
|
+
|
|
29
|
+
- [Install](#install)
|
|
30
|
+
- [Quick start](#quick-start)
|
|
31
|
+
- [Live demo](#live-demo)
|
|
32
|
+
- [Why dbsc-toolkit](#why-dbsc-toolkit)
|
|
33
|
+
- [Comparison](#comparison)
|
|
34
|
+
- [Why not JWT?](#why-not-just-use-jwt)
|
|
35
|
+
- [How it works](#how-it-works-the-30-second-version)
|
|
36
|
+
- [Who is this for?](#who-is-this-for)
|
|
37
|
+
- [Adding to an existing app](#adding-to-an-existing-app)
|
|
38
|
+
- [Protect your routes](#protect-your-routes)
|
|
39
|
+
- [Production checklist](#production-checklist)
|
|
40
|
+
- [API](#subpath-imports)
|
|
41
|
+
- [Security model](#security-model)
|
|
42
|
+
- [Going deeper](#going-deeper)
|
|
43
|
+
|
|
14
44
|
## Why dbsc-toolkit
|
|
15
45
|
|
|
16
46
|
Native DBSC ships on Chrome 146+ for Windows users with a TPM. That's about a third of internet users. Every other DBSC implementation gives the rest of your users nothing — they remain on plain bearer cookies.
|
|
17
47
|
|
|
18
|
-
This library is the
|
|
48
|
+
This library is among the first DBSC implementations to provide a Web Crypto fallback for browsers that don't support native DBSC. Same wire-level protection against cookie theft, same `requireProof()` guard server-side, key stored non-extractably in IndexedDB instead of a TPM. No biometric prompt, no user interaction, no checkbox.
|
|
19
49
|
|
|
20
|
-
- ✅ **Native W3C DBSC** on Chromium 146+ (TPM
|
|
21
|
-
- ✅ **Web Crypto polyfill** on Firefox, Safari, older Chromium — same protection, IndexedDB key
|
|
50
|
+
- ✅ **Native W3C DBSC** on Chromium 146+ (TPM on Windows, Secure Enclave on macOS)
|
|
51
|
+
- ✅ **Web Crypto polyfill** on Firefox, Safari, mobile, older Chromium — same protection, IndexedDB key
|
|
22
52
|
- ✅ **Express, Fastify, Hono, Next.js** adapters — all four ship in one package
|
|
23
53
|
- ✅ **Memory, Redis, Postgres** storage — pick one, swap with one line
|
|
24
54
|
- ✅ **`requireProof()`** per-request signature guard — works on every browser, body-hash defended against MITM
|
|
@@ -110,42 +140,19 @@ The native-DBSC column refers to a server that uses the spec without a polyfill
|
|
|
110
140
|
|
|
111
141
|
JWTs are still bearer tokens. Whoever has the bytes can replay them. Steal a JWT from a log line, an XSS, a leaked proxy header, a misconfigured intermediary — paste it into another machine and the server treats it as the legitimate user, exactly like a stolen cookie.
|
|
112
142
|
|
|
113
|
-
DBSC pins the session to a cryptographic key that lives on the user's device (TPM, Secure Enclave
|
|
143
|
+
DBSC pins the session to a cryptographic key that lives on the user's device (TPM on Windows, Secure Enclave on macOS, or — under the polyfill — a non-extractable IndexedDB key on every other browser). The token alone is no longer sufficient: the browser has to prove possession of the key on every refresh, and on every guarded request via `requireProof()`. The attacker's device has no key, so the replay fails and the session demotes within one cycle.
|
|
114
144
|
|
|
115
145
|
DBSC complements your existing auth (passwords, MFA, JWT/session cookies) — it does not replace them. It closes the *replay-after-issue* gap that bearer tokens cannot.
|
|
116
146
|
|
|
117
147
|
## How it works (the 30-second version)
|
|
118
148
|
|
|
119
149
|
```
|
|
120
|
-
|
|
121
|
-
─────── ──────
|
|
122
|
-
│ POST /login (username, password) │
|
|
123
|
-
│ ───────────────────────────────────────────────────▶ │
|
|
124
|
-
│ │ verify password
|
|
125
|
-
│ 200 + Secure-Session-Registration header │
|
|
126
|
-
│ ◀─────────────────────────────────────────────────── │
|
|
127
|
-
│ │
|
|
128
|
-
│ generate keypair │
|
|
129
|
-
│ (TPM / Secure Enclave / IndexedDB) │
|
|
130
|
-
│ │
|
|
131
|
-
│ POST /dbsc/registration │
|
|
132
|
-
│ Secure-Session-Response: <JWS containing pub key> │
|
|
133
|
-
│ ───────────────────────────────────────────────────▶ │
|
|
134
|
-
│ │ verify self-signature
|
|
135
|
-
│ │ store sessionId → pubkey
|
|
136
|
-
│ 200 + __Host-dbsc-session cookie (Max-Age 10 min) │
|
|
137
|
-
│ ◀─────────────────────────────────────────────────── │
|
|
138
|
-
│ │
|
|
139
|
-
│ every ~10 min, browser hits /dbsc/refresh │
|
|
140
|
-
│ signing a fresh server challenge with the key │
|
|
141
|
-
│ ───────────────────────────────────────────────────▶ │
|
|
142
|
-
│ │ verify signature
|
|
143
|
-
│ │ reissue cookie
|
|
144
|
-
│ 200 + fresh __Host-dbsc-session │
|
|
145
|
-
│ ◀─────────────────────────────────────────────────── │
|
|
150
|
+
Login → Key Registration → Cookie Issued → Challenge → Signature Verification → Session Refresh
|
|
146
151
|
```
|
|
147
152
|
|
|
148
|
-
|
|
153
|
+
On login the server responds with a `Secure-Session-Registration` header. The browser generates an ECDSA keypair in the TPM (Chrome) or IndexedDB (Firefox/Safari/polyfill), POSTs the public key, and gets back a short-lived session cookie. Every ~10 minutes the browser re-signs a fresh server challenge to renew the cookie. A copy of the cookie pasted into another device has no key — the next refresh fails and `requireProof()` 403s every guarded request immediately.
|
|
154
|
+
|
|
155
|
+
Full wire-format walk-through: [HOW-IT-WORKS.md](./HOW-IT-WORKS.md).
|
|
149
156
|
|
|
150
157
|
## Who is this for?
|
|
151
158
|
|
|
@@ -220,6 +227,16 @@ For apps with a stricter threat model (active MITM, log-spillage exposure), v2.8
|
|
|
220
227
|
|
|
221
228
|
The full threat boundary, the per-framework wiring (Fastify / Hono / Next.js), and the migration timeline for an existing app are in [docs/integrating-existing-auth.md](./docs/integrating-existing-auth.md) and [docs/per-request-signing.md](./docs/per-request-signing.md).
|
|
222
229
|
|
|
230
|
+
## Production checklist
|
|
231
|
+
|
|
232
|
+
- [ ] HTTPS enabled — DBSC requires `Secure` cookies; plain HTTP locks every user to `tier: "none"`
|
|
233
|
+
- [ ] Redis or PostgreSQL storage — `MemoryStorage` is lost on restart; bound browsers loop registration
|
|
234
|
+
- [ ] `requireProof()` on every authenticated route — read-only public routes need no guard
|
|
235
|
+
- [ ] `wrapFetch({ signBody: true })` on the client side for those routes (already the default in v2.8+)
|
|
236
|
+
- [ ] Replay cache enabled — `replayCache: new RedisReplayCache(redis)` in `createDbsc({...})`
|
|
237
|
+
- [ ] `trust proxy` already set — `install()` sets it, but verify if you have your own app config overriding it
|
|
238
|
+
- [ ] Polyfill script loaded — the `<script type="module">` tag; without it Firefox/Safari stay on `tier: "none"`
|
|
239
|
+
|
|
223
240
|
## Subpath imports
|
|
224
241
|
|
|
225
242
|
| Import | What it is |
|
|
@@ -240,7 +257,7 @@ Tree-shaking eliminates anything you don't import. Using Koa, Hapi, raw `http`,
|
|
|
240
257
|
|
|
241
258
|
| Tier | Mechanism | Protects against |
|
|
242
259
|
|------|-----------|------------------|
|
|
243
|
-
| `dbsc` | Native W3C DBSC, key in TPM
|
|
260
|
+
| `dbsc` | Native W3C DBSC, key in TPM (Windows) or Secure Enclave (macOS) | Cookie theft (XSS, network, logs, paste-to-other-browser) **and** infostealer malware reading the browser profile |
|
|
244
261
|
| `bound` | Web Crypto polyfill, non-extractable ECDSA P-256 key in IndexedDB | Cookie theft. Does not defeat infostealer malware on the user's machine. |
|
|
245
262
|
| `none` | No active / fresh binding | Nothing the cookie itself doesn't already do |
|
|
246
263
|
|
|
@@ -251,6 +268,23 @@ Tree-shaking eliminates anything you don't import. Using Koa, Hapi, raw `http`,
|
|
|
251
268
|
|
|
252
269
|
There is no third option, and **never gate a route on `tier === "dbsc"`** — that locks out every Firefox and Safari user (they can only reach `tier: "bound"`), and `requireProof()` already gives those browsers the same per-request guarantee via a signed proof. `requireProof()` is the one guard; it works on every browser. Read `res.locals.dbsc.tier` only if you want to *display* binding state in the UI — not to build a gate. Full guidance in [docs/security/best-practices.md](./docs/security/best-practices.md).
|
|
253
270
|
|
|
271
|
+
## Security model
|
|
272
|
+
|
|
273
|
+
`dbsc-toolkit` protects against:
|
|
274
|
+
|
|
275
|
+
- Stolen session cookies replayed from another device
|
|
276
|
+
- Stolen bearer tokens (same category — bearer == portable)
|
|
277
|
+
- Cookie exfiltration via XSS, network capture, log leakage, or proxy leakage
|
|
278
|
+
|
|
279
|
+
The Web Crypto polyfill (`tier: "bound"`) does **not** protect against:
|
|
280
|
+
|
|
281
|
+
- Malware already running on the user's machine with access to the browser profile
|
|
282
|
+
- Browser or OS compromise
|
|
283
|
+
|
|
284
|
+
Native DBSC (`tier: "dbsc"`) provides stronger protection against local malware because the private key lives inside the TPM or Secure Enclave — software cannot extract it even with admin access. The polyfill key is non-extractable to JavaScript, but the encrypted blob lives on disk and a sufficiently privileged attacker can reach it.
|
|
285
|
+
|
|
286
|
+
Neither tier is a substitute for HTTPS, input validation, strong passwords, or MFA.
|
|
287
|
+
|
|
254
288
|
## Going deeper
|
|
255
289
|
|
|
256
290
|
- **Concepts and protocol:** [HOW-IT-WORKS.md](./HOW-IT-WORKS.md)
|
|
@@ -267,7 +301,7 @@ There is no third option, and **never gate a route on `tier === "dbsc"`** — th
|
|
|
267
301
|
|
|
268
302
|
## Status
|
|
269
303
|
|
|
270
|
-
Verified end-to-end on Chrome 147 / Windows / TPM 2.0. Native DBSC
|
|
304
|
+
Verified end-to-end on Chrome 147 / Windows / TPM 2.0. Native DBSC requires Chromium 145+ on Windows or macOS Apple Silicon. The bound polyfill works on every browser with Web Crypto + IndexedDB (Firefox, Safari, mobile browsers, older Chromium). No third-party security audit yet. Production-readiness table and adoption guidance: [HOW-IT-WORKS.md#production-readiness](./HOW-IT-WORKS.md#production-readiness).
|
|
271
305
|
|
|
272
306
|
## License
|
|
273
307
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dbsc-toolkit",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.3",
|
|
4
4
|
"description": "Server-side Device Bound Session Credentials (DBSC) for Node.js plus a Web Crypto polyfill for Firefox, Safari, and older Chromium. Cookie-theft protection on every modern browser.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|