In a multi-tenant system, isolation is not a property of a table that happens to contain a tenant_id column. It is an end-to-end invariant: from identity and authorisation through cache, search, exports and asynchronous work.
Establish context first
User identity and tenant identity are different concerns. A user may belong to several organisations, and the “administrator” role normally has meaning only inside a particular tenant. Tenant context should come from a trusted mechanism and be bound to the request or job. A browser parameter does not become trustworthy merely because its syntax is valid.
Carry the boundary across the path
- Authorisation: check the right to perform the particular operation on the resource, not merely that a token passed at the gateway. An unknown tenant–resource combination should be denied by default.
- Data: every read and write path must apply the same tenant scope. A repository, view or row-level policy can provide a second barrier, but it does not excuse missing application checks.
- Cache and search: tenant identity belongs in the key namespace, index or filter. Plan invalidation when permissions change.
- Asynchronous work: messages and export jobs need an explicit, verified context. A worker should not infer it from a filename or the last request it happened to process.
- Observability: record the decision and technical identifiers for audit without copying tenant data into a common log.
How to verify it
Build tests with at least two tenants that have colliding local identifiers. Exercise a read after the cache is warm, exports, search, role changes and delayed message processing. A positive test shows that tenant A can see its data. The more valuable negative test demonstrates that A cannot read, alter or indirectly infer B's data.
A separate database per tenant creates a stronger boundary but increases the operational cost of migrations, reporting and fleet management. A shared store can be a sound decision when scoping is central, enforced and continuously tested. “Developers will remember” is not an isolation mechanism. It is a request addressed to a future incident.
Question for your project: where is trusted tenant context established, and in which parts of the path can it currently be lost or replaced?
Discuss this article
Share a constructive observation or an anonymised example. Comments are checked by AI before publication. Uncertain cases go to the moderator.