Skip to content

Architecture and request flows ​

zero-auth is a library inside your API process. It signs and verifies JWTs, provides Express middleware, and calls application-owned hooks for stateful refresh protection.

Responsibility boundary ​

text
Client
  │  bearer header or cookies
  ▼
Express application
  │  credential lookup, password hashing, business policy
  ▼
zero-auth
  │  sign, verify, extract, protect, authorize, refresh
  ▼
Application stores
  ├─ user database
  └─ Redis/database refresh state when rotation is enabled

The package never creates users, checks passwords, chooses a database, or decides resource ownership. Your application supplies a trusted user payload after its own credential checks.

Token lifecycle ​

text
login
  └─ application verifies credentials
      └─ zero-auth signs access + refresh tokens

API request
  └─ protect() extracts and verifies the access token
      └─ req.user is populated
          └─ authorize()/authorizePermissions() checks policy

refresh
  └─ refreshHandler() verifies the refresh token
      ├─ stateless mode: issue a new access token
      └─ rotation mode: atomically consume jti, then issue a new pair

Claims ​

Application claims are preserved through refresh:

ClaimPurpose
idRequired application user identifier
emailOptional application user email
roleOptional role checked by authorize()
permissionsOptional strings checked by authorizePermissions()
jtiToken identifier used for refresh rotation
fidStable refresh-token family identifier when rotation is enabled
iss / audOptional issuer and audience claims enforced by JWT policy
iat / expIssued-at and expiration timestamps

Do not put passwords, secrets, or unnecessary sensitive data in claims. JWT payloads are readable by anyone holding the token even though they are signed.

Middleware ordering ​

ts
app.use(express.json());
app.use(auth.csrf()); // Needed for cookie-authenticated writes.

app.patch(
  "/users/:id",
  auth.protect(),
  auth.authorizePermissions(["users:update"]),
  updateUserHandler,
);

app.use(auth.errorHandler); // Last.

CSRF checks whether auth cookies are present. Bearer requests still need protect and authorization, but do not need the cookie CSRF middleware.

Stateless and stateful modes ​

ModeShared storageReplay detectionBest fit
Bearer, no rotationNoNoSimple APIs and internal services
Cookie, no rotationNoNoSmall browser applications
Rotating refresh tokensYes for multiple instancesYesProduction sessions requiring replay response

An in-memory revocation store is suitable for tests and one-process development. Redis or a database is required when instances restart or requests can reach different processes.

Source layout ​

  • packages/zero-auth/src/core: JWT signing, verification, and decoding.
  • packages/zero-auth/src/middleware: protection, optional auth, roles, permissions, and CSRF.
  • packages/zero-auth/src/refresh: refresh handling and revocation-store helpers.
  • packages/zero-auth/src/cookies: cookie setting, clearing, and parsing.
  • packages/zero-auth/src/errors: typed errors and Express error responses.
  • packages/zero-auth/src/types: public configuration, payload, and Express augmentation types.
  • tests: unit, integration, security, rotation, and concurrency coverage.

Failure boundaries ​

Invalid access tokens fail with 401. Missing roles or permissions fail with 403. Invalid CSRF values fail with 403. A rotation-store failure fails closed with 401 AUTH_TOKEN_INVALID so the server does not issue untracked tokens.