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.

Fictional example. An API correctly filters its database by tenant, but a shared cache stores reports using only a tenant-local report number. Two tenants can both have report 42. The first request populates the cache; the second receives another tenant's result. The database did not fail. The isolation boundary ended before the cache.

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

  1. 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.
  2. 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.
  3. Cache and search: tenant identity belongs in the key namespace, index or filter. Plan invalidation when permissions change.
  4. 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.
  5. 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?

Further reading: AWS Well-Architected SaaS Lens: Tenant isolation; OWASP Authorization Cheat Sheet. The example is fictional; the recommendations synthesise design practices.