Skip to content

Authentication Architecture

This document describes the authentication and authorization architecture of Neemias, covering the complete login flow, tokens, sessions, and route protection.

Overview

Authentication is based on short-lived JWT access tokens combined with long-lived rotating refresh tokens. The backend (Cloudflare Workers) manages session creation, validation, and revocation. The frontend (React SPA) consumes the tokens and maintains the local session in IndexedDB.

Tokens and durations

TokenAlgorithmTTLStorage
Access TokenJWT HS256 (jose)15 minutesMemory + IndexedDB (frontend)
Refresh TokenOpaque (SHA-256 hash stored)7 daysauth_refresh_sessions (backend), IndexedDB (frontend)
CSRF TokenOpaqueSession-boundIndexedDB (frontend), sent as X-CSRF-Token

Authentication flow

1. Login

POST /api/v1/auth/login  { email, password }
  1. Frontend sends email and password to the backend.
    • If the user types a short identifier (admin, chamador, relatorios, cadastro), the frontend normalizes it to the corresponding email (admin@neemias.local, etc.).
  2. Backend validates the password using PBKDF2 (100,000 iterations, SHA-256).
    • Hash format: pbkdf2:<hexSalt>:<hexHash>.
  3. Backend creates an auth session:
    • Generates refresh token (opaque).
    • Stores SHA-256 hash of the refresh token in auth_refresh_sessions.
    • Generates CSRF token bound to the session.
    • Signs JWT HS256 access token with payload: userId, role, sessionId, csrfToken.
  4. Backend returns:
    • accessToken
    • accessTokenExpiresAt
    • refreshToken
    • csrfToken
    • user (payload with userId, displayName, role, status)
  5. Frontend persists session metadata in IndexedDB (sessions table) and user data in users.

2. Authenticated requests

Every authenticated request must include:

HeaderRequiredDescription
Authorization: Bearer <accessToken>YesJWT access token
X-Device-IdYesDevice identifier (required on all requests)
X-CSRF-TokenYes (cookie mode)CSRF token for mutation routes when AUTH_SESSION_MODE=cookie
Idempotency-KeyYes (mutations)Idempotency key for POST/PATCH

3. Refresh (token rotation)

POST /api/v1/auth/refresh  { refreshToken }
  1. Frontend detects expired access token (or near expiration) and calls the refresh endpoint.
  2. Backend validates the refresh token hash in auth_refresh_sessions.
  3. Backend applies refresh token rotation:
    • Revokes the previous refresh token (revoked_at, rotated_at).
    • Creates a new access/refresh pair.
    • Records replaced_by pointing to the new session.
  4. Frontend replaces local tokens with the new ones.

Malicious reuse detection: If an already-revoked refresh token is reused, the backend detects it and invalidates the user's entire session chain.

4. Revocation

POST /api/v1/auth/revoke  { refreshToken }

Explicitly revokes a session, marking revoked_at in auth_refresh_sessions.

5. Session validation

POST /api/v1/session/validate

Checks if the current session is active and returns its state. Used by the frontend for periodic validation.

Dev Mode

When AUTH_MODE=dev is configured in the backend:

  • Pre-configured fixed tokens from environment variables are accepted.
  • Real JWT is not required.
  • Facilitates local development and testing.

Important: In production, AUTH_MODE=jwt is mandatory (see Production Readiness Gates).

Password hashing

  • Algorithm: PBKDF2 (Password-Based Key Derivation Function 2).
  • Iterations: 100,000.
  • Hash: SHA-256.
  • Storage format: pbkdf2:<hexSalt>:<hexHash>.
  • History: The legacy backend used scrypt, which proved incompatible with the Cloudflare Workers environment. The migration to PBKDF2 was resolved in ADR-0003.

Session modes

ModeConfigurationBehavior
BearerAUTH_SESSION_MODE=bearer (default)Refresh token sent in response body; frontend stores in IndexedDB
CookieAUTH_SESSION_MODE=cookieRefresh token in httpOnly cookie; mutations require X-CSRF-Token

Module architecture

Backend (workers/src/)

FileResponsibility
modules/auth/password.tsPBKDF2 hashing and verification
modules/auth/sessionTokens.tsJWT signing/verification + refresh token
middleware/auth.tsJWT verification middleware + dev mode + role guard
db/queries.tsReusable auth and idempotency queries
routes/auth.tsLogin, refresh, revoke handlers

Frontend (app/src/)

FileResponsibility
app/context/AuthContext.tsxAuth state, roles, session
modules/auth/backendAuth.tsBackend-based authentication mode
modules/auth/sessionManager.tsLocal session management
modules/auth/sessionHydrator.tsSession hydration from IndexedDB
modules/auth/keyRotationService.tsLocal encryption key rotation

Sequence diagram

┌──────────┐         ┌──────────┐         ┌──────────────┐
│ Frontend │         │ Backend  │         │ D1 (SQLite)  │
└────┬─────┘         └────┬─────┘         └──────┬───────┘
     │                    │                      │
     │  POST /auth/login  │                      │
     │───────────────────▶│                      │
     │                    │  SELECT password_hash│
     │                    │─────────────────────▶│
     │                    │◀─────────────────────│
     │                    │                      │
     │                    │  PBKDF2.verify()     │
     │                    │                      │
     │                    │  INSERT session      │
     │                    │─────────────────────▶│
     │                    │◀─────────────────────│
     │                    │                      │
     │  { accessToken,    │                      │
     │    refreshToken,   │                      │
     │    csrfToken,      │                      │
     │    user }          │                      │
     │◀───────────────────│                      │
     │                    │                      │
     │  (15 min later)    │                      │
     │                    │                      │
     │  POST /auth/refresh│                      │
     │───────────────────▶│                      │
     │                    │  SELECT/UPDATE       │
     │                    │  session (rotate)    │
     │                    │─────────────────────▶│
     │                    │◀─────────────────────│
     │                    │                      │
     │  { newAccessToken, │                      │
     │    newRefreshToken }│                     │
     │◀───────────────────│                      │
     │                    │                      │
     │  POST /auth/revoke │                      │
     │───────────────────▶│                      │
     │                    │  UPDATE revoked_at   │
     │                    │─────────────────────▶│
     │                    │◀─────────────────────│
     │  { ok: true }      │                      │
     │◀───────────────────│                      │

Sources: workers/CONTEXT.md, ADR-0003, ADR-0007

Distributed under MIT License.