Multi-tenant applications share code and often share storage, but each tenant remains a security boundary. A verified session selects the active tenant; a request body or URL may identify a target but cannot self-authorize a tenant switch. Database predicates, cache keys, queue messages, file paths, and search documents must keep scope explicit. A cache key that omits tenant identity may return one organization’s record to another even when the database query was correct. Background jobs are more subtle: a worker may have broad service access, so it must re-establish the actor and tenant scope from a trusted job envelope and restrict every object it touches.
Tenant Scope in Cache and Background Work
Working case
Organizations 6 and 9 both have a case numbered 47. The API caches case summaries by the string case:47. Reviewer 29 from organization 6 loads a summary, then reviewer 62 from organization 9 receives the cached one. A later export worker receives tenantId 9 in a client-submitted payload and reads organization 9 data despite the requester belonging to 6. Fixing only the cache key is insufficient. Derive tenant scope from the authenticated actor when enqueueing, bind the job to that actor and authorized tenant, and check the same boundary when the worker executes.
Implementation boundary
function caseCacheKey(tenantId, caseId, permissionVersion) {
return `tenant:${tenantId}:case:${caseId}:policy:${permissionVersion}`;
}
console.log(caseCacheKey(6, 47, 3));
// Output: tenant:6:case:47:policy:3A scoped cache key prevents one class of collision, but authorization must run before protected cache reads. Include all attributes that alter the representation, such as user role or permission version, or do not cache that representation across users. Keep globally shared assets in a separate explicit namespace. Build job envelopes on the server after authorization; record requester and permitted tenant as server-authored metadata, and require workers to use tenant-scoped data access. Privileged maintenance jobs need a distinct audited path. If database row-level controls are used, connect normal requests with a role that cannot bypass them and set tenant context safely for each transaction.
Cost and boundaries
Adding a tenant dimension increases cache entries roughly with the number of active tenants and may lower hit rate, but it prevents cross-tenant reuse. Permission-version dimensions can create stale entries until eviction, so TTL and invalidation still matter. Tenant predicates need indexes for common access patterns; without them, shared tables scan more rows as tenant count grows. A worker check adds small per-job cost compared with the cost of a data leak. Load-test mixed-tenant traffic, not only one tenant at a time, and measure cache hit rate and query plans.
Failure trace
A developer patches the API cache key but leaves an image variant cache keyed only by image ID. A user who guesses a path can retrieve another tenant’s file from the edge. Inventory every store and delivery path, then scope or authorize each. Another defect trusts tenantId copied from an API request into a job; the worker runs with a privileged database role and bypasses the endpoint’s restriction. Construct the job envelope from verified server state, make the worker enforce scope, and test a forged tenant field.
Verification
- Same numeric IDs in separate tenants never share protected cache entries.
- Workers reject forged or stale tenant claims according to a documented policy.
- Normal request paths cannot silently bypass tenant-scoped database access.
Practice drill
Create tenant 6 and tenant 9 records with the same case ID. Alternate requests rapidly and inspect API, search, media, and edge-cache results. Submit a forged tenantId in an export request and verify the server ignores or rejects it. Replay a queued job after the requester loses access; define whether it continues under an already authorized immutable grant or rechecks current permission, and test the chosen rule. Check database sessions and worker identities for privileges that bypass normal tenant predicates.
Decision note
Make tenant scope server-derived and explicit in every storage, cache, and background boundary.
Common Mistakes
- Assuming tenant IDs are globally unique object IDs.
- Using a tenant-aware database query behind a tenant-blind cache key.
- Letting a privileged worker trust tenant scope from client payload.
Connected lessons
Authorization and Tenant Boundaries; Object-Level Authorization for Reads and Writes; Permission Revocation and Active Sessions; Private Export Authorization and Artifact Scope; Shared Cache Keys and Private Response Boundaries; Background Jobs and the Outbox Boundary; Search Permissions and Private Results.
Apply and check
Build Project: tenant-safe case export and review Web Development: authorization and data lifecycle decisions quiz.
Further connections
GraphQL Resolver Batching and Tenant Scope.
Further connections
Cache-Aside Fill Races and Version Guards; Negative Cache Entries and Stale-Read Contracts.
