Multi-Tenant SaaS Starter: 8-Dimension Production Readiness Audit

Not all multi-tenant starters are equal. Some ship user-auth only and call it multi-tenant. Others enforce org isolation but skip seat billing. The checklist below covers the eight dimensions that matter for a real B2B SaaS foundation — each grounded in what a shipped starter actually does, not what it promises.

Tenancy enforcement layer

Where does the starter actually enforce tenant isolation?

What good looks like: Server-side on every request — layout loads verify org membership, actions re-derive membership fresh, services check before any write. No trust placed on client-provided org IDs.

What starters often ship instead: Most starters scope data at the UI layer only (filter by org in the client, render only that orgs data). A forged request with a different org ID returns another tenants data.

Invite-token security

How are invite tokens stored, and what stops reuse?

What good looks like: Token is hashed (SHA-256) before storage; only the hash lives in the database. Single-use via conditional UPDATE whose WHERE clause rejects already-accepted and already-revoked rows. Atomic claim prevents double-click membership.

What starters often ship instead: Many starters store the raw token in the database (a DB leak exposes every pending invite) and check used in application code rather than at the database level, creating race conditions.

Seat enforcement choke point

Where is the seat limit actually checked — and does it happen atomically?

What good looks like: At invite acceptance time, behind a swappable BillingAdapter seam. The accept action queries current seat count, compares against plan limit, and only then commits the membership.

What starters often ship instead: Common pattern: seat limit checked in the billing dashboard UI, not at the point of membership creation. Or checked in the invite-creation action (before the invite is accepted), so the seat could fill between creation and acceptance.

RBAC hierarchy

Is the role hierarchy static (database-enforced) or dynamic (scattered conditionals)?

What good looks like: A single static capability matrix (owner, admin, member) with numeric ranks; every action calls requirePermission plus mayActOn/mayGrant. The matrix lives in exactly one place; adding a permission means adding one line.

What starters often ship instead: Starters often ship role strings with if/else checks scattered across components and actions. Adding a new permission means touching many files and hoping you did not miss one.

Audit trail design

Can the audit log be tampered with after the fact?

What good looks like: Append-only at the schema level — no UPDATE or DELETE grant exists on the table. Audit rows are written in the same service call as the mutation they record, with metadata that never contains raw tokens or secrets.

What starters often ship instead: Most starters either have no audit trail, or store audit rows in a regular table that application code can UPDATE — which defeats the purpose when a compliance buyer asks who changed what.

Auth rate limiting

Is brute-force protection included, and where does it run relative to password hashing?

What good looks like: Sliding-window limiter runs BEFORE password hashing (pre-check), so blocked requests never touch the expensive hash. Keyed per IP and email for login, per IP for signup. Deterministic via injectable clock for testing.

What starters often ship instead: Many starters have no rate limiting at all. Those that do often run the check after the hash, meaning blocked requests still burn CPU on every attempt.

Test posture

How many scenarios are actually tested, and what level?

What good looks like: 77 vitest tests covering auth flows, invite acceptance with seat enforcement, RBAC hierarchy and permission checks, rate-limiter sliding-window semantics, and failed-login behavior. Tests exercise services and actions directly, not just UI snapshots.

What starters often ship instead: Common: a handful of smoke tests that verify login works and signup creates a user without testing the edge cases (expired invites, seat overflow, role escalation attempts, sliding-window expiry).

Multi-instance readiness

What breaks when you scale past one process?

What good looks like: Rate limiter interface documents the multi-instance gap honestly and provides a seam (same interface, different backing store). Session storage and seat enforcement use the same database, not in-memory state.

What starters often ship instead: Starters that use in-memory sessions or in-memory rate limiting without documenting the gap — you discover it at the first deploy behind a load balancer, not at the README stage.

How to use this checklist

This is not a vendor comparison — it is a set of questions to ask any multi-tenant starter before you commit your B2B SaaS foundation to it. For each dimension, run the starters code, read the relevant source files, and verify the claim against reality.

Honest scope

This checklist covers application-layer tenancy. It does not cover infrastructure-layer isolation (separate databases per tenant, network-level isolation, per-tenant encryption keys) — those are enterprise concerns that add significant complexity and cost.

Related reading

Get in touch

Questions about the product, team licenses, or anything else? We'll respond within 48 hours.

Max 2000 characters

Stored in our own database — no third party. Deleted on request.