Uzair Manan
Case Study
All projects

RedAuth Auth template

An Express and TypeScript auth backend, published on npm as a template you copy into a new project with one command.

Role
Solo, backend
Timeline
Mar – Apr 2026
Stack
Express 5 · TypeScript · Prisma · Redis
Status
Published on npm
The Brief

Auth done once

Most auth tutorials stop at "sign a JWT". RedAuth is a template that goes the rest of the way: sessions you can list and revoke per device, refresh tokens that notice when they've been copied, and the rate limiting and email plumbing already wired.

  • 14 endpoints: signup, login, email verification, password reset, logout, logout everywhere, token rotation, profile and sessions.
  • One refresh token per device per user, so a session means one device.
  • Nothing secret stored in plain form: passwords are bcrypt at cost 12, tokens are stored as SHA-256 hashes.
  • Every input validated with Zod before it reaches a service.
14
Endpoints
119
Tests
3
Rate-limit
tiers
The Build

What I built

  1. Rotation with theft detection

    Using a refresh token deletes its record and issues a new pair. If a valid token arrives whose record is already gone, someone used a copy. Every session for that user is deleted and flagged as revoked in Redis.

  2. Instant revocation

    Access tokens are stateless, so logging out writes revoked-<tokenId> to Redis with a 30 minute TTL, longer than an access token lives. The auth middleware checks that key on every request.

  3. Sliding-window rate limits

    A Redis sorted set per IP, so limits hold across several server instances. Three tiers: 100 requests a minute everywhere, 10 per 5 minutes on auth routes, 3 per 5 minutes on anything that sends email.

  4. Email off the request path

    Verification, login alerts and password resets go through a BullMQ queue with three retries and exponential backoff. If a job fails for good, the orphaned token record is deleted.

Detail

The replay check

The token id lives inside the refresh token's payload, so the record is a primary-key lookup rather than a hash comparison across the table. A JWT that verifies but has no record is the signal.

if (!record && strict) {
  // A valid token with no record: it was already rotated.
  await prisma.token.deleteMany({ where: { userId: decoded.id } });
  await Promise.all(sessions.map((s) =>
    redis.set(`revoked-${s.id}`, "true", "EX", 60 * 30)));
}
Outcome

Where it landed

It's on npm as redauth at version 2.0.0, with a landing page, a Postman collection covering every route, a Docker setup, structured logging with pino, and 119 unit and integration tests that run against real Postgres and Redis containers.

It's also the project the later ones grew out of. FluxBuy reused its rate limiter idea and its rotation rule, and Invariant went the other way, to server-side sessions, because a token that spends money has to be revocable at once.