SkillByAIOpen interactive version →

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.