webcrypto-token
v0.0.1
Published
Tiny zero-dependency signed token (JWT alternative) using only Web Crypto HMAC-SHA256 — short-lived expiring claims for Cloudflare Workers, edge, browsers, Deno, Bun and Node
Maintainers
Keywords
Readme
webcrypto-token
Tiny, zero-dependency signed token library built only on the Web Crypto API (crypto.subtle, HMAC-SHA256). Sign and verify short-lived, expiring claims — a lightweight JWT alternative that runs the same everywhere: Cloudflare Workers (workerd), edge runtimes, browsers, Deno, Bun, and Node.js 18+. No jose, no jsonwebtoken, no Node crypto module. Written in TypeScript with typed claims.
Use it when you need to hand a trusted, short-lived claim across a boundary where session cookies don't travel — cross-subdomain auth, WebSocket / realtime connection tickets, signed URLs, magic links, or service-to-service handoffs — and the verifier only needs a shared secret, not your auth database.
Why
When a request crosses a boundary such as app.example.com → realtime.example.com, your session cookie is not sent. The issuer must sign a small, short-lived claim ("this is user u1, they may write") and pass it along, e.g. in a query string. A full JWT library is heavier than this needs, and symmetric HMAC signing is enough when the issuer and verifier share a secret.
- Format:
base64url(JSON payload).base64url(HMAC-SHA256 signature) exp(expiry) is added to the payload automatically on sign and checked on verify.- Signatures are compared in constant time (timing-safe).
- Stateless: no database lookup on verify.
Use cases
- One-time tickets for WebSocket or realtime server connections
- Cross-subdomain or cross-worker auth handoff without shared cookies
- Signed download links and short-lived invite links
- Email verification and magic-link tokens
- Passing permissions between microservices or edge workers
Install
npm install webcrypto-token
# or
pnpm add webcrypto-tokenUsage
import { signToken, verifyToken } from "webcrypto-token";
interface MyClaims { userId: string; canWrite: boolean }
// Issue (e.g. on your API server). ttlSeconds defaults to 60.
const token = await signToken<MyClaims>(
{ userId: "u1", canWrite: true },
{ secret: SECRET, ttlSeconds: 60 }
);
// Verify (e.g. on another worker). Returns claims (+exp) if valid, otherwise null.
const claims = await verifyToken<MyClaims>(token, SECRET);
if (claims) {
claims.userId; // string
claims.exp; // number (epoch seconds)
}API
| Function | Description |
| --- | --- |
| signToken<T>(claims, options) | Adds exp to the claims and signs them with HMAC-SHA256. options.ttlSeconds defaults to 60 |
| verifyToken<T>(token, secret) | Checks signature and expiry. Returns T & { exp } on success, otherwise null |
SignTokenOptions
| Field | Type | Description |
| --- | --- | --- |
| secret | string | HMAC signing secret |
| ttlSeconds? | number | Seconds until expiry. Default 60 |
Notes
- The payload is signed, not encrypted. Anyone holding the token can read the claims, so don't put secrets in them.
- Any
expyou pass inclaimsis overwritten fromttlSeconds. - Issuer and verifier must share the same secret. Use a long random value.
Development
pnpm install
pnpm test
pnpm build:distprepare runs obuild --stub, which writes a stub dist that points to the source. Run pnpm build:dist for a real build before publishing.
License
MIT
