Skip to content

bymaxone/nest-auth-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nest-auth-example

nest-auth-example

Reference application for @bymax-one/nest-auth
NestJS 11 · Next.js 16 · React 19 · Prisma 7 · PostgreSQL 18 · Redis 7 · Multi-Tenant SaaS Ready

library version license TypeScript strict Node.js 24+ NestJS 11 Next.js 16 React 19 Prisma 7 Tailwind 4

📦 Library · 🚀 Quick Start · ✅ Features · 🏗️ Architecture · 📖 Docs


✨ Overview

nest-auth-example is the canonical reference implementation of @bymax-one/nest-auth — the full-stack authentication library for NestJS, React & Next.js. Every feature the library ships is wired up end-to-end here, in a realistic, production-grade layout you can copy-paste into your own SaaS.

If the library is what to use, this repository is how to use it.

Why this repo exists

  • 🎯 Live demo of every library feature — registration, login, JWT refresh rotation, MFA (TOTP + recovery codes), Google OAuth, sessions with FIFO eviction, password reset (token & OTP modes), email verification, invitations, RBAC, multi-tenant isolation, platform admin context, brute-force lockout, WebSocket auth — all wired and runnable in under five minutes.
  • 🧱 Copy-paste friendly — module organization, repository implementations (Prisma), email providers (Mailpit / Resend), proxy & route handlers (Next.js App Router), React hook usage. The folder names are deliberately generic so you can lift them directly into your codebase.
  • 🧪 Production-grade test harness — 75 unit/integration suites (755 tests) + 26 API e2e suites (83 tests) + 23 Playwright specs (23 passing, no skips). Used by the library's maintainers to gate releases.
  • 🧬 100 % mutation score — Stryker mutation testing on both apps/api and apps/web. 2120 mutants killed, zero behavioural survivors. CI gates every PR on thresholds.break = 100 so no regression can land — see docs/stryker/HISTORY.md and docs/stryker/IMPLEMENTATION_PLAN.md.
  • 🔄 Always tracking latestmain is pinned to the latest @bymax-one/nest-auth minor; when the library ships a new release, this repo is updated alongside.
git clone https://github.com/bymaxone/nest-auth-example.git
cd nest-auth-example
pnpm install && pnpm infra:up && pnpm dev

Coverage rule: every public export from @bymax-one/nest-auth (server, shared, client, react, nextjs subpaths) is referenced from at least one file in this repository — enforced automatically in CI by scripts/audit-library-exports.mjs. See the Feature Coverage Matrix — 32/32 features green.


🔥 What's inside

🔐 Auth flows (apps/web UI + apps/api backend)

  • Register · Login · Logout — email/password with cookie-mode token delivery, anti-enumeration error shaping
  • Email verification (OTP) — 6-digit codes, cooldown timer, Mailpit-captured email
  • Password reset — both token (link in email) and otp (6-digit code) modes, same UI
  • JWT refresh rotation — silent iframe refresh + client-triggered refresh, grace window for concurrency
  • MFA enrollment — QR code render via qrcode, recovery codes generation + download
  • MFA challenge on login — TOTP and recovery-code paths, temp token in sessionStorage (never cookie)
  • Google OAuth — "Continue with Google" wired to the library's plugin; account linking on existing emails
  • Brute-force lockout — Redis atomic counters; surfaced via AUTH_ERROR_CODES.ACCOUNT_LOCKED
  • Session management — list active devices, revoke one, revoke all; FIFO eviction at session limit; new-session alert email
  • WebSocket notificationsws:// channel authenticated via Bearer token + Redis JTI revocation check, with tenant isolation

🏢 Multi-tenant + RBAC

  • Tenant isolation — every row carries tenantId; X-Tenant-Id header resolved server-side; slug→CUID resolver for the UI
  • Hierarchical rolesOWNER > ADMIN > MEMBER > VIEWER, enforced via @Roles() decorator + RolesGuard
  • Workspace switcher — Slack-style: same email in multiple tenants? Pick a workspace from the dropdown and the app logs you out and redirects you to the destination tenant's login — one JWT per tenant, never a live context swap
  • User invitations — admin invites by email + role; recipient flow consumes the token and sets a password
  • Account status enforcementUserStatusGuard blocks suspended users; admin UI toggles the flag

🛡️ Platform admin

  • Separate JWT contextJwtPlatformGuard rejects dashboard tokens; bearer-only delivery via sessionStorage
  • Tenant management — list tenants, drill into users per tenant
  • User suspension — flip an entire tenant's user to suspended / active; self-suspension is blocked at the UI

🎨 UI

  • Dark glass-morphism design system — Tailwind 4 + custom tokens + shadcn/ui primitives
  • React Hook Form + Zod — every form validates at the boundary; library-shipped error codes are translated for the user
  • Sonner toasts + inline errors — recoverable issues bubble as toasts; unrecoverable ones (expired invitation, broken link) stay inline
  • WS toast notifications<NotificationListener> consumes the gateway and renders ephemeral toasts
🌐 Browser (Chromium/Firefox/Safari)
   │  HttpOnly cookies + sessionStorage bearer (platform only)
   ▼
🎨 Next.js 16 (apps/web) — App Router, Turbopack
   │  · createAuthProxy (proxy.ts)
   │  · createSilentRefreshHandler / createClientRefreshHandler / createLogoutHandler
   │  · <AuthProvider> + useSession / useAuth / useAuthStatus
   ▼  same-origin /api/* rewrite
🚀 NestJS 11 (apps/api) — Express 5
   │  BymaxAuthModule.registerAsync({ jwt, mfa, oauth, sessions, … })
   │  · PrismaUserRepository       implements IUserRepository
   │  · PrismaPlatformUserRepository
   │  · MailpitEmailProvider / ResendEmailProvider  implements IEmailProvider
   │  · AppAuthHooks                implements IAuthHooks (audit log writer)
   ▼
🐘 PostgreSQL 18 + 🔴 Redis 7 + 📬 Mailpit (dev)

🚀 Quick Start

Prerequisites

Tool Version Notes
Node.js >= 24 .nvmrc pins it — nvm use is enough
pnpm >= 10.8 npm install -g pnpm@latest
Docker Compose v2 docker compose version should report v2.x

That's it — no sibling library checkout, no global links, no other system dependencies. The library is consumed from npm.

1. Clone & install

git clone https://github.com/bymaxone/nest-auth-example.git
cd nest-auth-example
pnpm install

2. Bring up infrastructure

pnpm infra:up   # postgres:5432 + redis:6379 + mailpit:8025 (UI) / 1025 (SMTP)

3. Configure secrets

cp .env.example apps/api/.env
# Generate the two secrets the lib refuses to start without:
echo "JWT_SECRET=$(openssl rand -hex 64)"            >> apps/api/.env
echo "MFA_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> apps/api/.env

Important

apps/web/.env.local must declare AUTH_JWT_SECRET_FOR_PROXY with the same value as JWT_SECRET — the Next.js proxy decodes the cookie locally so it can role-gate without round-tripping the API.

4. Migrate + seed

pnpm --filter @nest-auth-example/api prisma:migrate   # apply Prisma migrations
pnpm --filter @nest-auth-example/api prisma:seed      # 2 tenants, 8 tenant users, 1 platform admin

5. Run the stack

pnpm dev   # boots api (:4000) + web (:3000) in parallel
Endpoint What it serves
http://localhost:3000 Next.js web app
http://localhost:3000/auth/login?tenantId=acme Login page for the acme tenant
http://localhost:3000/platform/login Platform admin login (separate context)
http://localhost:4000/api/health API health probe — postgres, redis, library ok
http://localhost:8025 Mailpit UI — every dev email lands here

6. Sign in

Dev credentials only — never reuse outside local development.

Login Tenant Role Password
owner.acme@example.com acme OWNER Passw0rd!Passw0rd
admin.acme@example.com acme ADMIN Passw0rd!Passw0rd
admin.globex@example.com globex ADMIN Passw0rd!Passw0rd
platform@example.dev SUPER_ADMIN PlatformPassw0rd!

The seed prints all credentials in a banner — read it on first prisma:seed.

Full walk-through: docs/GETTING_STARTED.md


✅ Feature Coverage

Every @bymax-one/nest-auth capability is exercised. Each row links to the spec that locks the behavior.

🔐 Core authentication

Library feature Demonstrated in Tested by
Register + email verification (OTP) app/auth/register · app/auth/verify-email apps/api/test/register-and-verify.e2e-spec.ts
Login (cookie mode) + logout app/auth/login · <AuthProvider> apps/api/test/login-and-logout.e2e-spec.ts · apps/web/e2e/login-happy.spec.ts
JWT access + refresh rotation app/api/auth/silent-refresh · client-refresh apps/api/test/refresh-rotation.e2e-spec.ts
JWT revocation (Redis JTI blacklist) "Sign out everywhere" in dashboard/sessions apps/api/test/jwt-revocation.e2e-spec.ts
Password reset — token mode app/auth/forgot-passwordreset-password?mode=token apps/api/test/password-reset-token.e2e-spec.ts
Password reset — OTP mode Same UI with ?mode=otp apps/api/test/password-reset-otp.e2e-spec.ts
Wrong-password + anti-enumeration shape app/auth/login error mapping apps/web/e2e/login-wrong-password.spec.ts

🔒 MFA & OAuth

Library feature Demonstrated in Tested by
TOTP enrollment + QR + recovery codes dashboard/security apps/api/test/mfa-setup-challenge-disable.e2e-spec.ts
TOTP challenge on login app/auth/mfa-challenge apps/web/e2e/mfa-enroll-and-login.spec.ts
Recovery code consumption Same challenge page, recovery-code tab apps/api/test/recovery-codes.e2e-spec.ts
MFA disable dashboard/security apps/api/test/mfa-setup-challenge-disable.e2e-spec.ts
Google OAuth sign-in + linking "Continue with Google" on login + register apps/api/test/oauth-link.e2e-spec.ts

🏢 Multi-tenant & RBAC

Library feature Demonstrated in Tested by
Tenant isolation via X-Tenant-Id tenantAwareFetch + every API call apps/api/test/tenant-isolation.e2e-spec.ts
Workspace switcher (Slack-style re-auth) <TenantSwitcher> + GET /api/account/workspaces apps/web/e2e/tenant-switcher.spec.ts (logs out then redirects to /auth/login?tenantId=<slug>)
RBAC with @Roles() + hierarchy apps/api/src/projects/projects.controller.ts apps/api/test/rbac.e2e-spec.ts
User invitations dashboard/invitationsaccept-invitation apps/api/test/invitations.e2e-spec.ts · apps/web/e2e/invitations.spec.ts
Account status enforcement Admin suspend/unsuspend in platform UI apps/api/test/status-enforcement.e2e-spec.ts · apps/web/e2e/platform-users-suspend.spec.ts

🔁 Sessions & throttling

Library feature Demonstrated in Tested by
Active sessions list + revoke dashboard/sessions apps/api/test/sessions-list-revoke.e2e-spec.ts
FIFO eviction at session limit Auto-applied by lib config apps/api/test/session-fifo-eviction.e2e-spec.ts
New-session email alert Captured in Mailpit on every fresh login apps/api/test/login-and-logout.e2e-spec.ts (assertions)
Brute-force lockout Login surfaces ACCOUNT_LOCKED apps/api/test/brute-force-lockout.e2e-spec.ts
Per-route throttling AUTH_THROTTLE_CONFIGS + @nestjs/throttler wired in app.module.ts apps/api/test/throttle-demo.e2e-spec.ts

🛡️ Platform admin

Library feature Demonstrated in Tested by
Separate JWT context + login /platform/login apps/web/e2e/platform-login.spec.ts
Platform-only routes / shell /platform/* layout apps/web/e2e/platform-shell.spec.ts
Tenant listing in platform /platform/tenants apps/web/e2e/platform-tenants.spec.ts
Platform → tenant isolation Library guard rejects mix apps/api/test/platform-isolation.e2e-spec.ts

🔌 Integrations

Library feature Demonstrated in Tested by
WebSocket auth (Bearer token + JTI revocation) apps/api/src/notifications/notifications.gateway.ts apps/api/test/websocket-auth.e2e-spec.ts · apps/web/e2e/notifications-isolation.spec.ts
IAuthHooks for audit logging apps/api/src/auth/app-auth.hooks.ts apps/api/src/auth/app-auth.hooks.spec.ts
IEmailProvider (Mailpit dev, Resend prod) apps/api/src/auth/mailpit-email.provider.ts (+ resend) Smoke-tested in dev via Mailpit
Next.js Edge proxy (createAuthProxy) apps/web/proxy.ts apps/web/lib/require-auth.test.ts
React hooks (useSession/useAuth/useAuthStatus) apps/web/components/** apps/web/components/layout/topbar.test.tsx & friends

Note

The full coverage matrix (32 rows, every one ✅) is in docs/OVERVIEW.md §6.


🏗️ Architecture

Two independently deployable services + three infra services. The web app and API talk JSON over HTTP; session state lives entirely in HttpOnly cookies plus Redis.

┌─────────────────────────────────────────────────────────────────────┐
│                         Browser (User Agent)                        │
│                                                                     │
│  HttpOnly cookies: access_token, refresh_token, has_session         │
│  sessionStorage (platform only): platform_access_token              │
└─────────────────────────────┬───────────────────────────────────────┘
                              │ HTTPS (same registrable domain)
                              ▼
┌─────────────────────────────────────────────────────────────────────┐
│  Next.js 16 (apps/web) — Turbopack, App Router                      │
│  • Edge proxy `createAuthProxy` → role/cookie gating                │
│  • API route handlers:                                              │
│      /api/auth/silent-refresh  (createSilentRefreshHandler)         │
│      /api/auth/client-refresh  (createClientRefreshHandler)         │
│      /api/auth/logout          (createLogoutHandler)                │
│  • <AuthProvider> + useSession / useAuth / useAuthStatus            │
│  • <NotificationListener> WS consumer                               │
└─────────────────────────────┬───────────────────────────────────────┘
                              │ same-origin rewrite to :4000
                              ▼
┌─────────────────────────────────────────────────────────────────────┐
│  NestJS 11 (apps/api) — Express 5                                   │
│  • BymaxAuthModule.registerAsync({                                  │
│      jwt, mfa, oauth.google, sessions, bruteForce,                  │
│      passwordReset, platform, invitations, roles,                   │
│      controllers, tokenDelivery: 'cookie', tenantIdResolver })      │
│  • PrismaUserRepository           implements IUserRepository        │
│  • PrismaPlatformUserRepository   implements IPlatformUserRepository│
│  • MailpitEmailProvider           implements IEmailProvider (dev)   │
│  • ResendEmailProvider            implements IEmailProvider (prod)  │
│  • AppAuthHooks                   implements IAuthHooks (audit)     │
│  • NotificationsGateway (WS, guarded by WsJwtGuard)                 │
└──────┬─────────────────────────────────────┬────────────────────────┘
       │                                     │
       ▼                                     ▼
┌─────────────────┐                 ┌────────────────────┐
│  PostgreSQL 18  │                 │     Redis 7        │
│   (port 5432)   │                 │    (port 6379)     │
│                 │                 │                    │
│ users           │                 │ sessions,          │
│ platform_users  │                 │ brute-force        │
│ invitations     │                 │ counters, OTPs,    │
│ projects        │                 │ JWT revocation,    │
│ audit_logs      │                 │ refresh tokens     │
│ tenants         │                 │                    │
└─────────────────┘                 └────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│  Mailpit (dev) — port 1025 (SMTP) + 8025 (UI)                       │
│  Captures every outbound email; nothing escapes the host machine.   │
└─────────────────────────────────────────────────────────────────────┘

Repository layout

nest-auth-example/
├── apps/
│   ├── api/                          # NestJS 11 backend
│   │   ├── prisma/
│   │   │   ├── schema.prisma         # User, PlatformUser, Tenant, Invitation, AuditLog, Project
│   │   │   ├── migrations/
│   │   │   └── seed.ts               # Demo tenants + users + platform admin
│   │   ├── src/
│   │   │   ├── auth/                 # ← LIBRARY WIRING
│   │   │   │   ├── auth.module.ts                # BymaxAuthModule.registerAsync()
│   │   │   │   ├── auth.config.ts                # resolves config from Env
│   │   │   │   ├── prisma-user.repository.ts     # implements IUserRepository
│   │   │   │   ├── prisma-platform-user.repository.ts
│   │   │   │   ├── mailpit-email.provider.ts     # implements IEmailProvider (dev)
│   │   │   │   ├── resend-email.provider.ts      # implements IEmailProvider (prod)
│   │   │   │   ├── app-auth.hooks.ts             # implements IAuthHooks (audit log)
│   │   │   │   ├── app-jwt-auth.guard.ts         # composes JwtAuthGuard + UserStatusGuard
│   │   │   │   └── auth-exception.filter.ts
│   │   │   ├── account/              # /api/account/*  — me, change password, delete
│   │   │   ├── tenants/              # /api/tenants/*  — list, resolve slug→id
│   │   │   ├── projects/             # /api/projects/* — RBAC + tenant scope demo
│   │   │   ├── platform/             # /api/platform/* — admin tenant + user management
│   │   │   ├── invitations/          # /api/invitations/* — create + accept
│   │   │   ├── users/                # /api/users/* — tenant-admin user listing
│   │   │   ├── notifications/        # WS gateway + REST debug endpoints
│   │   │   ├── health/               # /api/health — postgres + redis + lib version
│   │   │   ├── debug/                # /api/debug/* — tenant-isolation demos
│   │   │   ├── prisma/               # PrismaService + module
│   │   │   ├── redis/                # Redis provider + module
│   │   │   ├── logger/               # Pino + nestjs-pino wiring
│   │   │   ├── config/               # Zod env schema + ConfigService<Env, true>
│   │   │   ├── app.module.ts
│   │   │   └── main.ts
│   │   └── test/                     # supertest e2e (26 specs, 83 tests)
│   │
│   └── web/                          # Next.js 16 frontend
│       ├── app/
│       │   ├── (auth)/               # public auth pages (shared shell)
│       │   │   ├── login/
│       │   │   ├── register/
│       │   │   ├── forgot-password/
│       │   │   ├── reset-password/   # both ?mode=token and ?mode=otp
│       │   │   ├── verify-email/
│       │   │   ├── mfa-challenge/
│       │   │   └── accept-invitation/
│       │   ├── dashboard/            # protected — uses useSession()
│       │   │   ├── account/
│       │   │   ├── security/         # MFA setup + disable
│       │   │   ├── sessions/         # active devices, revoke
│       │   │   ├── invitations/      # admin invites
│       │   │   ├── projects/         # RBAC demo
│       │   │   └── team/
│       │   ├── platform/             # platform-admin-only area
│       │   │   ├── login/
│       │   │   ├── tenants/
│       │   │   └── users/
│       │   ├── api/auth/
│       │   │   ├── silent-refresh/route.ts   # createSilentRefreshHandler
│       │   │   ├── client-refresh/route.ts   # createClientRefreshHandler
│       │   │   └── logout/route.ts           # createLogoutHandler
│       │   ├── layout.tsx            # <AuthProvider>
│       │   └── providers.tsx
│       ├── proxy.ts                  # createAuthProxy()
│       ├── lib/
│       │   ├── auth-client.ts        # createAuthClient() + tenant slug→CUID resolver
│       │   ├── auth-errors.ts        # translateAuthError(code)
│       │   ├── platform-auth.ts      # platform bearer-token flow
│       │   └── env.ts                # typed env, validated at boot
│       ├── components/
│       │   ├── auth/                 # forms, MFA QR, OtpInput, PasswordInput
│       │   ├── notifications/        # NotificationListener (WS toast)
│       │   ├── layout/               # sidebar, topbar, tenant switcher
│       │   └── ui/                   # shadcn primitives
│       └── e2e/                      # Playwright (21 specs)
│
├── docker/
│   ├── postgres/init.sql             # CREATE DATABASE example_app;
│   └── redis/redis.conf              # eviction = volatile-lru
│
├── docs/                             # 11 long-form docs + 16 guidelines
│   ├── OVERVIEW.md                   # product spec + feature coverage matrix
│   ├── GETTING_STARTED.md            # five-minute quickstart
│   ├── FEATURES.md                   # walkthrough of each demonstrated capability
│   ├── ARCHITECTURE.md               # deeper dive
│   ├── ENVIRONMENT.md                # every env var, defaults, validation
│   ├── DATABASE.md                   # schema rationale + migration strategy
│   ├── REDIS.md                      # key namespaces + TTLs
│   ├── EMAIL.md                      # how to swap providers
│   ├── OAUTH_GOOGLE.md               # Google OAuth setup walkthrough
│   ├── DEPLOYMENT.md                 # production checklist
│   ├── TROUBLESHOOTING.md
│   ├── RELEASES.md                   # which lib version each branch tracks
│   └── guidelines/                   # 16 per-stack guideline files (AI-friendly)
│
├── docker-compose.yml                # postgres + redis + mailpit (dev)
├── docker-compose.test.yml           # ephemeral stack for CI / e2e
├── .env.example
├── package.json                      # workspace root
├── pnpm-workspace.yaml
├── AGENTS.md                         # full agent / contributor spec
├── CLAUDE.md                         # quickref for AI agents
└── README.md                         # ← you are here

🧱 Tech Stack

NestJS 11 Next.js 16 React 19 TypeScript strict Node 24+ Prisma 7 Postgres 18 Redis 7 Tailwind 4 pnpm 10 Docker Compose v2 Jest 30 Vitest 4 Playwright 1

Layer Choice Why
Auth @bymax-one/nest-auth@^1.0.2 The library this repo demonstrates
Backend runtime Node.js ≥ 24 Library minimum; native node:crypto for scrypt/AES
Backend framework NestJS 11 on Express 5 Library peer dep
Database PostgreSQL 18 Most common SaaS choice; first-class tenant-isolation patterns
Cache / sessions Redis 7 via ioredis Library peer dep — session store, JWT blacklist, brute-force counters
ORM Prisma 7 Type-safe, idiomatic in NestJS
Email (dev) Mailpit Local SMTP capture for testing flows
Email (prod) Resend (reference) Pluggable via IEmailProvider — swap with SES, Postmark, etc.
Frontend Next.js 16 App Router Library peer dep; demonstrates /nextjs subpath
UI framework React 19 Library peer dep; demonstrates /react subpath
Styling Tailwind 4 + shadcn/ui De-facto modern default
Forms react-hook-form 7 + Zod 4 Boundary validation, the lib's preferred pairing
Logging Pino 10 via nestjs-pino Structured JSON, request-id correlation, env-driven redact paths
Tests (api) Jest 30 + supertest NestJS standard
Tests (web unit) Vitest 4 Fast HMR test runner; ESM-first
Tests (web e2e) Playwright 1 Cross-browser end-to-end
Container runtime Docker Compose v2 Single-command local stack
Package manager pnpm 10 Matches library; first-class workspace support

🧪 Testing & Quality

This repo is held to a similar bar as the library itself — every demonstrated flow has a regression-locking test, and CI runs the full matrix on every PR.

Suite Test count What it covers
pnpm --filter @nest-auth-example/api test 540 in 34 suites Unit + integration — repositories, hooks, services, guards, crypto utilities, no-op fallbacks, DTO surface
pnpm --filter @nest-auth-example/web test 701 in 50 files Vitest — auth client, error mapping, components, layout primitives
pnpm --filter @nest-auth-example/api test:e2e 88 in 27 suites supertest e2e — every auth flow + security headers against real Postgres + Redis (test stack)
pnpm --filter @nest-auth-example/web test:e2e 23 (no skips) Playwright — login, MFA, invitation, password reset, platform admin, workspace switch, OAuth Google click-through, WS isolation, web security headers
Total 1 352 tests Every @bymax-one/nest-auth subpath (server, shared, client, react, nextjs) is consumed by real application code, so the typecheck gate breaks immediately if any subpath export changes

Verification gates (run before every PR)

pnpm typecheck          # tsc --noEmit across all workspaces → 0 errors
pnpm lint               # ESLint flat config                  → 0 errors, no suppressions
pnpm format:check       # Prettier                            → clean
pnpm test               # unit + integration                  → 540 + 701 passing
pnpm audit:exports      # library export audit                → exit 0 (190 exports covered)

End-to-end gates

pnpm infra:test:up                                   # ephemeral test stack
pnpm --filter @nest-auth-example/api test:e2e        # 88 supertest e2e
pnpm --filter @nest-auth-example/web test:e2e        # Playwright (auto-starts api + web)
pnpm infra:test:down                                 # tear down

🔎 Library export audit

pnpm audit:exports
# [export-usage-check] ✓ All 190 exports are referenced in apps/ (26 ignored)

Every public symbol exported by @bymax-one/nest-auth — across all five subpaths (server, shared, client, react, nextjs) — must appear at least once in the apps/ source tree. The script scripts/audit-library-exports.mjs enforces this automatically.

Why it exists. A reference application is only valuable if it is complete. Without enforcement, it is easy to add a new guard, hook, or utility to the library and forget to demonstrate it — leaving users without a working example. The audit closes that gap: a PR that ships a new export without wiring it into the example will fail CI before it can be merged.

How it works.

  1. Discovers all subpaths dynamically from node_modules/@bymax-one/nest-auth/dist/.
  2. Parses each index.d.ts with regex to extract every exported symbol name.
  3. Walks the full apps/api/ and apps/web/ source trees and checks for a word-boundary occurrence of each name.
  4. Exits 0 when every symbol is found; exits 1 with a diff showing what is missing.

Intentional suppressions. Twenty-six symbols — internal injection tokens, structural mock interfaces, and low-level proxy utilities — are excluded via .audit-ignore.json. Each entry carries a reason string and a link to the tracking issue that records the design decision. The CI job treats any entry without a reason as a build error.

CI integration. The export-usage-check job in .github/workflows/ci.yml runs on every push and pull request:

- run: node scripts/audit-library-exports.mjs

Note

The --webpack flag was removed from the apps/web scripts in May 2026 once the library shipped to npm. Turbopack (the Next.js 16 default) now resolves the library's subpath exports correctly. See apps/web/next.config.mjs for context.


⚙️ Configuration

Everything is environment-variable driven. The API refuses to start on invalid configuration — Zod validates every value at boot.

Backend (apps/api/.env)

Variable Required Default Purpose
NODE_ENV yes development production enables cookie Secure, hides stack traces
API_PORT no 4000 Nest HTTP port
WEB_ORIGIN yes http://localhost:3000 CORS allowlist
DATABASE_URL yes Prisma connection string
REDIS_URL yes ioredis connection string
JWT_SECRET yes openssl rand -hex 64 — ≥ 64 chars
MFA_ENCRYPTION_KEY yes openssl rand -base64 32 — 32-byte base64
EMAIL_PROVIDER no mailpit mailpit or resend
SMTP_HOST / SMTP_PORT / SMTP_FROM conditional localhost:1025 / no-reply@… Required when EMAIL_PROVIDER=mailpit
RESEND_API_KEY conditional Required when EMAIL_PROVIDER=resend
OAUTH_GOOGLE_CLIENT_ID / OAUTH_GOOGLE_CLIENT_SECRET / OAUTH_GOOGLE_CALLBACK_URL optional Both ID and SECRET required to enable Google OAuth
PASSWORD_RESET_METHOD no token token or otp
LOG_LEVEL no info debug, info, warn, error

Frontend (apps/web/.env.local)

Variable Required Purpose
INTERNAL_API_URL yes URL the Next.js server proxies to (no /api prefix) — usually http://localhost:4000
AUTH_JWT_SECRET_FOR_PROXY yes Same value as API's JWT_SECRET — the proxy decodes cookies locally
NEXT_PUBLIC_API_URL yes Browser-visible API base — usually http://localhost:3000/api
NEXT_PUBLIC_WS_URL yes WebSocket base — usually ws://localhost:3000
NEXT_PUBLIC_OAUTH_GOOGLE_ENABLED no true to show the "Continue with Google" button

Full reference: docs/ENVIRONMENT.md


📦 Library consumption

This repo consumes @bymax-one/nest-auth from npm — no sibling checkout, no global links, no workspace tricks. The wiring lives in a single module:

// apps/api/src/auth/auth.module.ts (excerpt)
BymaxAuthModule.registerAsync({
  imports: [ConfigModule, PrismaModule, RedisModule, LoggerModule],
  inject: [ConfigService, PrismaService, REDIS_CLIENT, Logger],
  useFactory: (config, prisma, redis, logger) => buildAuthOptions(config, prisma, redis, logger),
});
// apps/web/proxy.ts (excerpt)
import { createAuthProxy } from '@bymax-one/nest-auth/nextjs';

export default createAuthProxy({
  apiBase: env.INTERNAL_API_URL,
  jwtSecret: env.AUTH_JWT_SECRET_FOR_PROXY,
  // … cookie names, login paths, public routes …
});
// apps/web/app/layout.tsx (excerpt)
import { AuthProvider } from '@bymax-one/nest-auth/react';

return (
  <html>
    <body>
      <AuthProvider apiBase="/api">{children}</AuthProvider>
    </body>
  </html>
);

➡ See the full module wiring in apps/api/src/auth/ and the React side in apps/web/app/providers.tsx.

Important

Library fidelity is a release gate. This repo never reshapes the library's DTOs, never re-implements its guards, and never maintains a parallel error-code map. If the library changes a signature, this repo is updated to match — not patched to keep the old shape. See docs/guidelines/nest-auth-guidelines.md.


🛡️ Security defaults

Every default here is on the side of safety. Tweak them only when you have a stronger constraint than the library's threat model.

Default Where Why
HttpOnly + SameSite cookies Library cookie config in apps/api/src/auth/auth.config.ts XSS-resistant token storage
CORS allowlisted to WEB_ORIGIN apps/api/src/main.ts No wildcard origins, ever
Helmet headers + csp: false apps/api/src/main.ts App Router handles CSP; Helmet covers everything else
Pino redact on tokens, headers, body apps/api/src/logger/logger.module.ts Never log secrets, OTPs, refresh tokens, passwordHash
Tenant isolation read-only on header Library tenantIdResolver User input can't dictate tenantId
Validation on every request body class-validator DTOs + Zod for boundaries No raw any reaches a service
exactOptionalPropertyTypes + strict TS tsconfig.base.json undefined vs missing is explicit
Env validated at boot apps/api/src/config/env.schema.ts Wrong env stops the app, not a 500 at runtime

➡ Full policy: docs/guidelines/security-privacy-guidelines.md


🐳 Docker

The dev stack runs three services, all bound to 127.0.0.1:

Service Image Host port(s) Volume Purpose
postgres postgres:18-alpine 5432 pgdata Primary DB
redis redis:7-alpine 6379 rdata Sessions, brute-force counters, JWT blacklist, OTPs
mailpit axllent/mailpit@… 1025, 8025 Dev SMTP capture + UI at http://localhost:8025

The test stack (docker-compose.test.yml) mirrors the same images but uses alternate ports (55432, 56379, 58025) and tmpfs volumes for ephemeral CI runs — runs alongside the dev stack without collision.

pnpm infra:up         # start dev stack
pnpm infra:down       # stop (keep data)
pnpm infra:nuke       # stop AND wipe volumes
pnpm infra:test:up    # ephemeral test stack
pnpm infra:logs       # tail all containers

🚢 Deployment

The example targets a two-service topology. See docs/DEPLOYMENT.md for the full checklist.

  • apps/api → any Node-24 host (Fly.io, Railway, ECS, Kubernetes) with Postgres + Redis reachable
  • apps/web → Vercel, Netlify, or self-hosted Node — set INTERNAL_API_URL to the API service URL
  • Same registrable domain for both services so cookies travel — use a subdomain like app.example.com for web and api.example.com for api, then configure cookies.resolveDomains accordingly
  • Cookie flags in prod: Secure: true, SameSite=Lax for OAuth callback compat
  • JWT rotation: the library supports jwt.previousSecrets for zero-downtime rotation
  • Email: flip to EMAIL_PROVIDER=resend and configure SPF/DKIM/DMARC on your sender domain
  • Audit logs: ship the audit_logs table to your SIEM via Postgres logical replication or a CDC tool

🗺️ Where to go next


🤝 Contributing

Issues and PRs are welcome. Because this is a reference application, the bar for changes is:

"Does this make the demonstration of @bymax-one/nest-auth clearer or more complete?"

Generic refactors that obscure library usage will be declined. See CONTRIBUTING.md and AGENTS.md for the full process.

# Clone
git clone https://github.com/bymaxone/nest-auth-example.git
cd nest-auth-example

# Install
pnpm install

# Verify
pnpm typecheck && pnpm lint && pnpm format:check && pnpm test

# Run
pnpm infra:up && pnpm dev

🔒 Security policy

If you find a security vulnerability — in either this example or the library — please do not open a public issue. Email support@bymax.one with details.

We respond promptly. See SECURITY.md in the library for the disclosure timeline.


📄 License

MIT © Bymax One

Library source: @bymax-one/nest-auth — MIT.


Built with ❤️ by Bymax One to demonstrate @bymax-one/nest-auth.

About

Reference implementation for @bymax-one/nest-auth — full-stack multi-tenant auth (JWT, MFA, OAuth, sessions, RBAC, invitations) with NestJS 11, Next.js 16, Prisma, Redis and TypeScript strict mode.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors