Establish trusted tenant context at entry
Derive the active tenant from an authenticated membership relationship and validate any requested tenant selection against it. A tenant identifier in a header or URL is a selector, not proof of membership. Bind the verified context to the request and ensure downstream services receive it through an authenticated mechanism.
Decide how users belonging to multiple tenants switch context. Avoid ambiguous defaults, and make the current context clear in interfaces that perform writes or exports. Support and administrative identities require separately reviewed privileges; an internal network location alone does not justify access to every tenant.
Choose a storage isolation model
Separate databases, separate schemas, and shared tables offer different isolation and operational tradeoffs. A shared table design needs reliable tenant scoping on every access path. Database row security can add protection, but its behavior depends on roles, ownership, policies, and connection management.
PostgreSQL superusers and roles with BYPASSRLS bypass row security. Table owners normally bypass it too, although FORCE ROW LEVEL SECURITY subjects owners to policies; it does not constrain superusers or BYPASSRLS roles. Use a restricted application role, scope tenant context to the transaction, and test connection pool reuse so one request cannot leave context active for another.
| Surface | Isolation requirement | Useful test |
|---|---|---|
| Database | Verified tenant condition and restricted role | Cross tenant read and write attempts |
| Cache | Tenant included in cache identity | Same resource key in two tenants |
| Object storage | Scoped paths and access checks | Foreign object URL access |
| Queue | Verified tenant context per message | Reused worker across tenants |
Isolate jobs, caches and exports
Carry tenant context through jobs, event handlers, search indexes, analytics projections, and generated exports. A background worker should validate its message contract and authorize the intended operation instead of trusting an arbitrary tenant field. Keep deduplication and idempotency scopes explicit when identifiers may repeat across tenants.
Include tenant identity in cache keys and invalidation operations. Apply access rules to relationship traversal, counts, and error messages as well as record bodies. Aggregated information can still reveal another tenant's activity. Review temporary files and signed download URLs because they often outlive the request that created them.
Test isolation and resource fairness separately
Build adversarial tests using identical local resource identifiers in two tenants. Exercise every route and job type with mismatched context, missing context, and a revoked membership. Include exports, retries, dead letter replay, and privileged support paths in the test inventory.
Data isolation and capacity isolation are related but distinct. A tenant can stay within its data boundary while exhausting a shared queue or database pool. Apply appropriate quotas, concurrency limits, and observability by tenant without creating unbounded metric cardinality. Review onboarding, offboarding, and deletion as lifecycle operations across all derived stores.