Production-grade authentication API built with Express 5, TypeScript, Prisma/PostgreSQL, and Redis.
This project models what a production-grade core auth service looks like when session lifecycle, replay resistance, operational safety, and change discipline are treated as first-class concerns.
It is intentionally focused on authentication depth, not identity-platform breadth. The goal is to show how a serious backend service should be designed when correctness, observability, and maintainability matter more than feature count.
- API base URL:
https://auth-api-production-a97b.up.railway.app - Swagger UI:
/docs - OpenAPI JSON:
/docs.json - Health probes:
/healthand/ready
Many portfolio auth APIs stop at registration, login, and a basic JWT flow. This one goes further by modelling the production concerns that usually decide whether an auth service is trustworthy in practice:
- first-class server-side sessions
- refresh-token rotation with replay detection
- explicit session revocation and compromise handling
- contract-driven API documentation
- split quality and integration gates in CI
- First-class
Sessionrecords with publicsessionIdidentifiers instead of exposing token internals. - Chain-aware refresh-token rotation that detects replay and marks compromised sessions inactive.
- Strict JWT validation with separate access and refresh secrets, enforced issuer, audience, and token type.
- Contract-driven OpenAPI output, typed environment validation, and infrastructure-backed integration tests in GitHub Actions.
- Redis-backed rate limiting with in-memory fail-soft behaviour and structured request correlation.
- Prometheus-compatible operational metrics with local Prometheus/Grafana assets.
- Reproducible
k6load scenarios with a published benchmark baseline for the auth lifecycle.
docs/adr/0001-session-lifecycle.mddocs/adr/0002-refresh-token-rotation.mddocs/adr/0003-rate-limit-fail-soft.mddocs/threat-model.md
- Proof snapshot:
docs/assets/auth-api-proof-overview.svg - Architecture decisions:
docs/adr/ - Threat model:
docs/threat-model.md - Benchmark report:
docs/benchmarks/auth-benchmark.md - Observability guide:
docs/observability.md - Grafana dashboard:
deploy/observability/grafana/dashboards/auth-api-dashboard.json
Latest benchmark baseline:
25.01 req/saggregate request throughput684.36msp95 request latency900.44msp99 request latency100%scenario check pass rate across session and replay flows
- protected
mainbranch with requiredqualityandintegrationchecks - GitHub secret scanning and push protection enabled
- Dependabot security updates enabled
- CodeQL analysis on pull requests and
main - dependency review on pull requests
- coordinated disclosure guidance in
SECURITY.md
flowchart LR
A["POST /v1/auth/sessions"] --> B["Create session record"]
B --> C["Issue access token + refresh token"]
C --> D["GET /v1/auth/me"]
C --> E["POST /v1/auth/tokens/refresh"]
E --> F["Rotate refresh token"]
F --> G["Invalidate previous token in chain"]
G --> H{"Refresh token replayed?"}
H -- "No" --> C
H -- "Yes" --> I["Mark session compromised"]
I --> J["Reject protected requests"]
The service ships:
- first-class
Sessionrecords - refresh-token rotation with replay detection
- explicit JWT claims for access and refresh tokens
- typed environment validation with fail-fast startup
- contract-driven OpenAPI output
- structured audit logging and request correlation
- Redis-backed rate limiting with in-memory fail-soft fallback
- Prometheus-compatible metrics gated by configuration
Base contract:
POST /v1/auth/registerPOST /v1/auth/sessionsPOST /v1/auth/tokens/refreshPOST /v1/auth/sessions/current/revokeGET /v1/auth/meGET /v1/auth/sessionsDELETE /v1/auth/sessions/:sessionIdDELETE /v1/auth/sessionsGET /healthGET /readyGET /metricsGET /docsGET /docs.json
Core response patterns:
- login and refresh return
{ accessToken, refreshToken, user, session } - register returns
{ user } - revoke endpoints return
204 No Content - errors return
{ error: { code, message, details?, correlationId } }
- Access and refresh tokens use different secrets.
- JWT validation enforces issuer, audience, and token type.
- Refresh tokens are stored as SHA-256 hashes, never as plaintext.
- Refresh rotation is chain-aware.
- Reusing an already-consumed refresh token marks the session as compromised and revokes its active tokens.
- Protected routes validate both the access token and the current server-side session state.
Main layers:
src/routes: HTTP compositionsrc/controllers: request orchestrationsrc/services: auth and session business rulessrc/repositories: persistencesrc/contracts: request/response schemas and OpenAPI source of truthsrc/config: validated environment and infrastructure clients
Important runtime pieces:
requestIdmiddleware for correlation IDs- request logger with structured logs
- auth middleware with strict bearer parsing
- Redis-first rate limiting with observable fallback
- graceful shutdown for HTTP server, Prisma, and Redis
Copy .env.example to .env and provide real secrets:
DATABASE_URL="postgresql://auth_user:auth_password@localhost:5432/auth_api"
ACCESS_TOKEN_SECRET="replace-this-with-a-strong-access-secret-123456"
REFRESH_TOKEN_SECRET="replace-this-with-a-strong-refresh-secret-12345"
ACCESS_TOKEN_EXPIRES_IN="15m"
REFRESH_TOKEN_EXPIRES_IN="7d"
JWT_ISSUER="auth-api"
JWT_AUDIENCE="auth-api-clients"
PORT=3000
REDIS_URL="redis://localhost:6379"
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX_REQUESTS=100
LOG_LEVEL="info"
TRUST_PROXY=0
DOCS_ENABLED=true
METRICS_ENABLED=false
METRICS_AUTH_TOKEN=""
BCRYPT_ROUNDS=10Start infrastructure:
docker compose up -d postgres redisInstall dependencies, generate Prisma Client, and run the API:
npm ci
npm run prisma:generate
npm run prisma:migrate:deploy
npm run devThe same migration command is used in CI, so local and remote validation stay aligned.
The default public hosting target for this project is Railway.
Deployment automation is implemented through .github/workflows/deploy.yml and supports:
- published GitHub releases for production promotion
- manual dispatch for intentional non-production deployments
- exact-ref verification before any deployment
- smoke validation for
/health,/ready, and/docs.json - a pinned Railway CLI version for deterministic release promotion
Deployment setup material:
The public demo deployment is live at https://auth-api-production-a97b.up.railway.app, with public docs available at /docs and /docs.json.
Operational model:
- CI in
.github/workflows/ci.ymlprotectsmainwithqualityandintegration - production deploys happen only from published GitHub releases
- manual
Deployruns are reserved for staging/homolog-style environments and rejectproduction - the deploy workflow re-verifies the exact ref being promoted with lint, typecheck, build, coverage, and integration tests before
railway up - production smoke validation is mandatory and fails clearly if
RAILWAY_PUBLIC_URLis missing
Enable metrics locally with METRICS_ENABLED=true and expose:
GET /metrics
If metrics are enabled on any non-local environment, prefer setting METRICS_AUTH_TOKEN or keeping the route private at the network layer instead of exposing raw Prometheus output publicly.
Local observability assets:
docs/observability.mddeploy/observability/docker-compose.ymldeploy/observability/grafana/dashboards/auth-api-dashboard.json
Bring up Prometheus and Grafana locally with:
docker compose -f deploy/observability/docker-compose.yml up -dThe repository includes a reproducible k6 benchmark for the session lifecycle and refresh replay path:
- scenario:
tests/load/auth-lifecycle.k6.mjs - report:
docs/benchmarks/auth-benchmark.md
Run it with:
npm run prisma:migrate:deploy
docker compose -f docker-compose.yml -f docker-compose.benchmark.yml up --build --abort-on-container-exit --exit-code-from k6 k6npm run dev: run the API withtsxwatch modenpm run build: compile TypeScript todist/npm run start: run the compiled servernpm run lint: lint with Biomenpm run typecheck: strict TypeScript checknpm test: fast unit and contract suitenpm run test:integration: integration suite against real Postgres and Redisnpm run test:coverage: Vitest coverage runnpm run prisma:generate: regenerate Prisma Clientnpm run prisma:migrate:deploy: apply committed migrations safely
The default npm test command is infrastructure-free and covers:
- environment parsing
- token issuance and verification
- auth middleware behaviour
- rate limiter fallback behaviour
- health and readiness semantics
- OpenAPI contract generation
- refresh replay compromise logic
The integration suite runs in GitHub Actions as the integration job and can be executed locally with the same script:
npm run test:integrationThat suite expects local PostgreSQL and Redis to be available and uses the same Prisma deploy migration step as CI.
The repository includes:
- a production-oriented
Dockerfile - a
docker-compose.ymlwithapp,postgres, andredis - a Railway deployment workflow and smoke validation path
To run the full stack with Compose:
docker compose up --buildThis iteration intentionally does not implement:
- MFA / TOTP
- password reset
- email verification
- OAuth / social login
The goal is a strong core auth service, not a full identity platform yet.
Special thanks to Marcos Pont for the technical support, thoughtful feedback, and engineering conversations that helped sharpen the quality of this project.