Lesson 21 / 25
Multi-Tenant Isolation
Keeping customers' data apart.
Tenant context everywhere
In a multi-tenant SaaS, a missing tenant filter can leak one customer's data to another. Derive the tenant ID from the authenticated identity (session or verified token claim), never from a request parameter the client can change. Isolation options range from separate databases (strongest, costly) to separate schemas to shared tables with a tenant column. With shared tables, add a safety net such as PostgreSQL row-level security (RLS), which filters rows by a per-connection setting even if application code forgets a WHERE clause. Apply tenant scoping to caches, search indexes, file storage paths, background jobs and logs too, and be explicit about cross-tenant admin access with extra auditing.
Row-level security as a backstop
PostgreSQL; the app sets the tenant for each transaction from the verified identity.
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant_id')::uuid)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);
-- per request, inside a transaction, using the tenant from the session or token:
BEGIN;
SELECT set_config('app.tenant_id', '6f1c2b0e-9a1d-4c3e-8f00-1b2c3d4e5f60', true); -- true = local to this transaction
SELECT * FROM invoices WHERE id = 42; -- returns nothing if it belongs to another tenant
COMMIT;RLS has bypasses
Table owners and superusers bypass RLS unless you use FORCE ROW LEVEL SECURITY and a non-owner application role. Check the PostgreSQL docs for your version and test isolation explicitly.
Quick check: Where should an API take the current tenant ID from?
- A tenant_id query parameter
- The authenticated session or verified token
- The Referer header
- A value in localStorage
Answer
The authenticated session or verified token — Client-controlled values can be changed to another tenant's ID.