Design tenant isolation for a B2B SaaS so one customer can never read another's data.
Key Talking Points
- ✓Derive tenant context from the authenticated session/JWT — never from a client-supplied tenant id (that's IDOR).
- ✓Enforce isolation at the data layer with Postgres Row-Level Security, not just app-code WHERE clauses (defense in depth).
- ✓Offer schema- or database-per-tenant for high-isolation/regulated tiers; shared tables for the long tail.
- ✓Per-tenant encryption keys (envelope encryption) for sensitive fields so a query leak isn't a plaintext leak.
- ✓Authorize every object access against the tenant; test explicitly with cross-tenant access attempts in CI.
- ✓Per-tenant rate limits + quotas to prevent noisy-neighbor and resource-exhaustion DoS.
- ✓Tag all logs/audit events with tenant id for forensics and to detect cross-tenant access.
The core challenge is that a single bug in application code (a missing WHERE tenant_id = ?) can expose every other tenant's data. Defense-in-depth means enforcing isolation at multiple layers so no single mistake is catastrophic.
The strongest server-side control is Row-Level Security (RLS) in Postgres — database-level policies that automatically filter rows based on the current session's tenant context. Even if application code forgets a filter, the database enforces it. This is much harder to accidentally bypass than app-layer filters alone.
Critically: always derive tenant context from the authenticated session/JWT, never from a client-supplied parameter (that would be an IDOR — a user could trivially supply a different tenant ID).
References