All case studies
Portfolio demoMay 4, 2026

Multi-Tenant SaaS Platform with Auth, Workspaces, and Stripe Billing

A production-grade B2B foundation — multi-tenant data isolation, role-based access, Stripe-billed subscriptions, and admin tooling — tested against the failure modes that break early SaaS launches in production.
Next.js 16
MUI v9
Auth.js v5
Neon Postgres
Stripe
Vercel

Business outcome. Product teams launch B2B SaaS without rebuilding the same plumbing every time. Auth, workspaces, role-based access, Stripe billing, and audit-grade tenant isolation are wired up end-to-end on day one, so engineering time goes into the actual product. The boring-but-load-bearing parts — dropped webhooks, last-admin guards, edge-runtime session validation — are already handled, with documented failure modes.

Built as the foundation B2B SaaS organizations launch on top of, not under. The interesting work isn't the features list; it's the failure modes that take SaaS products down in production — a dropped Stripe webhook, an admin who removes themselves, a session that has to validate at the edge before pg or bcrypt are reachable.

Pipeline

Scroll to zoom · click-drag to pan · double-click to reset.

What's built

  • Email/password auth with bcrypt and JWT sessions; edge-compatible middleware protects /dashboard/*.
  • Multi-tenant by design. Every server query is scoped by an active-organization context derived from a signed cookie and re-validated as a real membership on every request. Org IDs are never accepted from the client.
  • RBAC with admin / member and a can(role, permission) helper.
  • Member invites, role changes, removal with a transactional last-admin guard so a workspace can never become admin-less.
  • Stripe-billed subscriptions per workspace. Checkout, Customer Portal, signature-verified webhooks, "Sync from Stripe" recovery for missed events. Stripe is the source of truth: duplicate-subscription guards and recovery paths derive state by asking Stripe, never the local DB.
  • Edge-safe middleware split. lib/auth.config.ts is the edge-safe shared config used by the proxy (where pg and bcrypt aren't available); lib/auth.ts extends it with the credentials provider. Same callbacks, same JWT, two execution environments.
  • Settings: display name, password change, workspace rename, danger-zone deletion that cancels the Stripe sub on the way out.
  • EU-region deployable out of the box. Neon's eu-central regions and Vercel's fra1 functions cover the data-residency clauses most B2B buyers want; nothing in the hot path is pinned to a US region.

Tradeoffs

  • Email/password + bcrypt, not magic links or passkeys. Boring, portable, deployable without a third-party identity provider. The Auth.js v5 wiring leaves a clean slot for OAuth or WebAuthn the first time a customer asks.
  • Stripe is the source of truth, not the local DB. "Sync from Stripe" is one API call per recovery: cheap insurance against dropped webhooks and the only honest answer to "what does this customer actually have access to right now". The DB is a cache, treated as one.
  • Edge-safe config split. lib/auth.config.ts exists because pg and bcrypt can't bundle on the edge runtime; duplicating one config file is the cost of running auth in middleware before the heavy modules load.

Live demo →