2.14.1 DBSC ยท Device Bound Session Credentials library for Node.js

Stop stolen session cookies from being replayed.

dbsc-toolkit is the server-side Device Bound Session Credentials (DBSC) library for Node.js. The cookie still copies. The device key it needs to refresh does not. A session lifted to another machine dies on the next request.

Verified on real hardwareChrome 147 with TPM 2.0, refresh cycle included
9 framework adaptersExpress, Fastify, Hono, Next.js, NestJS, Koa, SvelteKit, Electron, node:http
Apache-2.0, no lock-inEvery line readable; polyfill covers Firefox and Safari

The problem

A session cookie trusts whoever holds it

That single property drives most account takeovers. Infostealer malware lifts the cookie off disk, the attacker pastes it into their own browser, and they are you. No password, no MFA prompt. DBSC binds the session to a key that never leaves the device, so a copied cookie has nothing to sign with.

the mechanism
// 1. at login, the browser mints a keypair in the TPM
//    and registers the public half. the private key
//    never leaves the chip.
// 2. every refresh and every guarded request needs a
//    fresh signature from that key.
// 3. a replayed cookie from another device cannot sign,
//    so the server demotes it:
tier: "dbsc"  // real device, verified
tier: "none"  // replay rejected, 403

The protocol

How the binding holds

The server is reactive. It sets one header after login and answers two endpoints; the browser drives the rest. You never write the protocol routes.

1
Register

Key born in hardware

At login the browser creates an EC P-256 keypair in the TPM or Secure Enclave and registers the public half. The private key never leaves the chip.

2
Refresh

Proof every cycle

The bound cookie is short-lived. Before each renewal the browser signs a fresh server challenge. A device without the key cannot refresh.

3
Guard

One call per route

requireProof() demands a per-request signature on sensitive routes, closing the gap between refreshes. Same guard on every browser.

Coverage

Every browser, one server

Native DBSC is Chromium-only: Windows from Chrome 145 (TPM), macOS from Chrome 147 (Secure Enclave). The Web Crypto polyfill covers everything else, so you read one tier and don't branch per browser. Run native-only with bound: false.

dbsc

Hardware key in the TPM or Secure Enclave. Chromium on Windows (Chrome 145+) or macOS (Chrome 147+).

Remote cookie theft defeated
On-device infostealer defeated
bound

Non-extractable IndexedDB key. Firefox, Safari, older Chromium.

Remote cookie theft defeated
On-device infostealer not defeated
none

Unbound or stale session. The bare cookie, like any normal app.

Remote cookie theft not defeated
On-device infostealer not defeated

Adapters

DBSC for any Node.js framework

Every adapter is a thin shell over the same framework-agnostic core. Pick yours, or wire the core into anything when no adapter exists.

DBSC for Express

dbsc-toolkit/express

The reference adapter. install() mounts the protocol routes; bind() starts a session.

const dbsc = createDbsc({ storage });
dbsc.install(app);
await dbsc.bind(res, sessionId, { userId });

DBSC for Fastify

dbsc-toolkit/fastify

A plugin that registers @fastify/cookie and the protocol routes for you.

const dbsc = createDbsc({ storage });
await dbsc.install(app);
await dbsc.bind(reply, sessionId, { userId });

DBSC for Hono

dbsc-toolkit/hono

Web-standard middleware. Runs on Node, Bun, Deno and Cloudflare Workers.

const dbsc = createDbsc({ storage });
dbsc.install(app);
await dbsc.bind(c, sessionId, { userId });

DBSC for Next.js

dbsc-toolkit/nextjs

App Router. Export the middleware, read the session inside route handlers.

// middleware.ts
export default dbsc.middleware();
// route: const s = await dbsc.getSession(req);

DBSC for NestJS

dbsc-toolkit/nestjs

A module mounts the protocol; DbscGuard protects routes; DbscService binds.

imports: [DbscModule.forRoot({ storage })]
// @UseGuards(DbscGuard) on a route
// this.dbsc.bind(res, sessionId, { userId })

DBSC for Koa

dbsc-toolkit/koa

Middleware over Koa's raw request and response objects.

const dbsc = createDbsc({ storage });
dbsc.install(app);
await dbsc.bind(ctx, sessionId, { userId });

DBSC for SvelteKit

dbsc-toolkit/sveltekit

A handle hook answers the routes; bind and guard inside actions.

// hooks.server.ts
export const handle = dbscHandle({ storage });
// await requireProof()(event) in a +server

DBSC for Electron

dbsc-toolkit/electron

The Electron main process plays server for your renderer. A bridge mounts the protocol on a custom scheme via protocol.handle; the renderer reaches the bound tier with the client SDK (native dbsc needs an https origin and hardware key store Electron rarely has).

const dbsc = createElectronDbsc({ storage });
const handleDbsc = dbsc.protocolHandler();
protocol.handle("app", (request) => handleDbsc(request));

Any other server, via raw node:http or the core

dbsc-toolkit/node

No adapter for your framework? The generic node:http handler works with any server that exposes the Node request and response objects, and the framework-agnostic core wires into anything.

const handle = dbsc.handler();
createServer(async (req, res) => {
  if (await handle(req, res)) return; // protocol route answered
  // ... your routes; read getDbscSession(req)
});

Install

Install and bind in minutes

One dependency, optional framework and storage peers. Memory storage to start; Redis or Postgres for production.

npm install dbsc-toolkit
View on npm
payment.js
// guard a sensitive route. a replayed cookie
// from another device is rejected on arrival.
app.post("/payment", dbsc.requireProof(), (req, res) => {
  res.json({ ok: true, tier: res.locals.dbsc.tier });
});

Specification

A spec, not just a library

The wire protocol is written up as a language-neutral specification with round-trip test vectors. Node is the reference implementation; the spec is what lets a conforming DBSC server be built in any language.

Language-neutral spec

Header grammar, JWS and JSON shapes, the storage and cookie contracts, and error codes. Read the spec

Round-trip test vectors

Known inputs and expected outputs, so any implementation self-checks without a browser. Test vectors

Verified end to end

Tested against Chrome 147 on real TPM 2.0 hardware, including the refresh cycle. How it works