import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
const REFRESH_TTL_MS = 1000 * 60 * 60 * 24 * 30;
export function hashToken(raw: string): string {
return createHash("sha256").update(raw).digest("hex");
}
export function signAccessToken(userId: string, sessionId: string): string {
return jwt.sign({ sub: userId, sid: sessionId }, ACCESS_SECRET, {
expiresIn: ACCESS_TTL,
});
}
export async function issueRefreshToken(
store: RefreshTokenStore,
userId: string,
familyId: string
): Promise<string> {
const raw = randomBytes(48).toString("base64url");
await store.save({
tokenHash: hashToken(raw),
userId,
familyId,
used: false,
rotatedTo: null,
expiresAt: Date.now() + REFRESH_TTL_MS,
});
return raw;
}
import { randomUUID } from "crypto";
import { hashToken, issueRefreshToken } from "./tokens";
export class TokenReuseError extends Error {}
interface RefreshRecord {
tokenHash: string;
userId: string;
familyId: string;
used: boolean;
rotatedTo: string | null;
expiresAt: number;
}
export class RefreshTokenStore {
private byHash = new Map<string, RefreshRecord>();
async save(record: RefreshRecord): Promise<void> {
this.byHash.set(record.tokenHash, record);
}
async revokeFamily(familyId: string): Promise<void> {
for (const rec of this.byHash.values()) {
if (rec.familyId === familyId) rec.used = true;
}
}
async rotate(rawToken: string): Promise<{ userId: string; next: string }> {
const record = this.byHash.get(hashToken(rawToken));
if (!record || record.expiresAt < Date.now()) {
throw new TokenReuseError("unknown or expired token");
}
if (record.used) {
// an old token was replayed: treat the whole lineage as compromised
await this.revokeFamily(record.familyId);
throw new TokenReuseError("refresh token reuse detected");
}
const next = await issueRefreshToken(this, record.userId, record.familyId);
record.used = true;
record.rotatedTo = hashToken(next);
return { userId: record.userId, next };
}
async startSession(userId: string): Promise<{ familyId: string; token: string }> {
const familyId = randomUUID();
const token = await issueRefreshToken(this, userId, familyId);
return { familyId, token };
}
}
import { Router } from "express";
import { RefreshTokenStore, TokenReuseError } from "./store";
import { signAccessToken } from "./tokens";
export function authRoutes(store: RefreshTokenStore) {
const router = Router();
const cookieOpts = {
httpOnly: true,
secure: true,
sameSite: "strict" as const,
path: "/auth/refresh",
};
router.post("/login", async (req, res) => {
const user = await authenticate(req.body.email, req.body.password);
if (!user) return res.status(401).json({ error: "invalid credentials" });
const { familyId, token } = await store.startSession(user.id);
res.cookie("rt", token, cookieOpts);
res.json({ accessToken: signAccessToken(user.id, familyId) });
});
router.post("/refresh", async (req, res) => {
const presented = req.cookies?.rt;
if (!presented) return res.status(401).json({ error: "no refresh token" });
try {
const { userId, next } = await store.rotate(presented);
res.cookie("rt", next, cookieOpts);
res.json({ accessToken: signAccessToken(userId, "") });
} catch (err) {
if (err instanceof TokenReuseError) {
res.clearCookie("rt", cookieOpts);
return res.status(401).json({ error: "session revoked" });
}
throw err;
}
});
return router;
}
declare function authenticate(email: string, password: string): Promise<{ id: string } | null>;
This snippet shows the conceptual mechanics of a JWT access + refresh token scheme where refresh tokens are rotated on every use and stolen tokens are detected by tracking a reuse. Short-lived access tokens keep authorization decisions cheap and stateless, but they cannot be revoked once issued, so the refresh token becomes the long-lived credential that must be handled carefully. The pattern here treats refresh tokens as single-use: each successful refresh invalidates the old token and issues a fresh pair, and any attempt to use an already-consumed token is treated as a compromise signal.
In tokens.ts the two token types are minted with very different lifetimes. signAccessToken produces a stateless 15-minute JWT carrying only the subject and a session id, while issueRefreshToken generates a random opaque string, hashes it with sha256, and stores the hash in RefreshTokenStore keyed to a familyId. Storing only the hash means a database leak does not immediately hand an attacker usable tokens. The familyId links every token descended from a single login, which is what makes reuse detection possible.
The RefreshTokenStore in store.ts models the persistence a real system would push into Redis or Postgres. Each record tracks whether it has been used and which token it was rotatedTo. rotate is the core operation: it looks up the presented token by hash, and if the record is already used it calls revokeFamily to kill the entire lineage — this is the reuse-detection branch. A legitimate client only ever holds the newest token, so a second use of an old token means either a replay or a leaked copy, and revoking the whole family logs out the attacker and the victim together, forcing a clean re-login.
The auth routes tab wires this into Express. /refresh reads the refresh token from an httpOnly cookie, calls store.rotate, and on success sets a new cookie and returns a new access token. When rotate throws TokenReuseError, the handler clears the cookie and responds 401, so the client is forced back through login. The trade-off is statefulness: refresh tokens require server-side storage and lookups, unlike the stateless access token, but that is precisely what enables revocation. Pitfalls to watch include races from concurrent refreshes (a mobile app firing two refreshes at once can trip false reuse) and always scoping cookies with httpOnly, secure, and sameSite.
Related snips
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
json.array! @posts do |post|
json.cache! ['v1', post], expires_in: 1.hour do
json.id post.id
json.title post.title
json.excerpt post.excerpt
json.published_at post.published_at
Fragment caching for expensive JSON serialization
#!/usr/bin/env bash
set -euo pipefail
export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"
Secrets management with environment isolation and Vault
package files
import (
"context"
"time"
Presigned S3 upload URLs (AWS SDK v2)
<%# private stream: turbo signs the serialized record name %>
<%= turbo_stream_from current_user %>
<section class="notifications">
<h1>Notifications</h1>
Turbo Streams + authorization: signed per-user stream name
Share this code
Here's the card — post it anywhere.