An engineering method for a multi-tenant real-time reporting platforms we build and operate, through Platform to Platform and 24×7 Operations Desk.
The challenge. Authentication and authorisation to a tenant's data are different controls, and a multi-tenant read path can enforce the first rigorously while enforcing the second inconsistently.
Context
We build and operate multi-tenant platforms: real-time reporting and risk consoles where one system serves many tenants, each seeing its own live operational data (money in, exposure, cohort flags) in a single portal instead of a console per tenant. Consolidation is the commercial point of multi-tenancy. It is also the security burden: every tenant’s data sits in the same tables, behind the same APIs, separated by nothing physical. What keeps one tenant out of another’s numbers is code.
Tenant scoping on that code is something we audit deliberately: how, mechanically, each read is constrained to the caller’s tenant. It is a capability we apply to any multi-tenant estate, and what follows is the method itself.
The problem
Nothing has to be on fire for this class of defect to bite. That is precisely why it is dangerous.
Strong authentication does not settle it. Authentication answers who is this. It does not answer is this caller allowed to read this tenant’s rows. Those are two different controls, and a system can have the first fully in place while the second is enforced inconsistently, path by path, at the discretion of whoever wrote each query.
Tenant scoping fails quietly. An unscoped read does not crash, does not log an error, and passes every functional test written from a single tenant’s perspective, because with one tenant in the test fixture there is no wrong data to leak. Multi-tenant test fixtures and negative access tests can expose this defect before production. Single-tenant tests cannot establish that isolation holds.
So the audit question is narrow and mechanical: for every read a platform serves, where exactly is the tenant constraint applied? In the storage key, in the query, in application code after the fetch, or nowhere?
What the audit does
The audit enumerates every read path and classifies each by its scoping mechanism:
- Scoped by key. The tenant is part of the primary or partition key, so an tenant-specific lookup is explicit. Broader queries, credentials and access controls still need review; the key alone does not guarantee isolation.
- Scoped by query. The tenant appears as a filter the developer must remember to include. Correct today, one refactor away from wrong.
- Scoped in application code. Data is fetched broadly and filtered after the fact. The weakest category that still functions.
- Unscoped. No constraint on the path at all.
That map is what turns tenant isolation from per-query discipline into a set of enforced controls: tenant-aware keys, constrained queries and appropriate authorisation. Tests must exercise cross-tenant access as well as valid requests.
One failure mode sits at the root of the classification, and no API-level review catches it: a tenant identifier missing from a primary key lets one tenant’s rows collide with another’s. Every query against such a table can look correct while the defect lives in the schema itself. It is the audit’s whole thesis in one row: tenant scoping has to be enforced where the data lives, in keys and queries, not only where the request arrives.
The same audit method looks for the siblings that travel with unscoped reads: a scheduler that fires once across all tenants when it should fire per tenant, and PII or credentials written to INFO-level logs where they should be redacted.
Audit outputs
The method produces a reviewable set of outputs:
- Every read is traceable to its scoping mechanism, so answering “how is this query tenant-constrained” is a lookup.
- Scoping is enforced at the schema and query level wherever the storage model allows it, so the correctness of tenant isolation does not depend on each new query remembering a filter.
- The audit is a repeatable artefact: a checklist of read paths and scoping categories that reruns against new services as a platform grows.
The method is domain-independent, and that is the point worth stating plainly. The method can be applied to a healthcare records platform, a government multi-tenant estate, or any SaaS product where organisations share infrastructure and must never share data. The vocabulary changes (patients, agencies, accounts); the mechanics of the question do not. Who computes the key, who writes the query, and where the tenant constraint physically lives are the same three questions in every domain.
What correct scoping looks like
Two rules carry most of the weight, and both are cheaper to adopt at design time than to retrofit across a live estate.
Authentication and authorisation to a tenant’s data are separate concerns, designed and tested as separate concerns. Strong identity verification is easy to mistake for completeness: if the login is hard, the system feels secure. A hard login says nothing about whether a caller can read another tenant’s rows. The second question has to be asked systematically, from the first service onward.
Tenant identity belongs in every primary key by rule, decided once at schema design time. A missing tenant identifier in a key is usually just an ordinary table designed before the rule was in place. A schema convention costs nothing to adopt on day one and is expensive to retrofit. We treat “tenant in the key” as the default that requires justification to deviate from.