TL;DR: Tenant isolation must be enforced at every data access path, not only in the controller. Resolve tenant context from a trusted identity, authorize actions against tenant-scoped resources, constrain queries, isolate caches and jobs, and record security-relevant decisions.
Start with the Threat Model
List what one tenant must never see or change: records, files, search results, invoices, exports, background-job payloads, logs, and analytics. Then list privileged workflows such as support access, billing administration, and cross-tenant reporting.
The dangerous bug is usually not a missing login. It is an object-level authorization failure where a valid user changes an identifier and receives another tenant’s resource. Every resource lookup needs both identity and tenant context.
Resolve Tenant Context Once
Tenant context may come from a verified session, a signed token claim, a mapped custom domain, or an API credential. Do not trust a tenant ID supplied only in a request body or query string. If a request includes multiple tenant signals, detect disagreement and reject it.
Pass a typed tenant context into the service layer. Avoid letting each controller invent its own resolution rules; inconsistency is how one endpoint becomes the bypass.
Enforce at the Data Boundary
Every tenant-owned table should have a tenant identifier, an index that supports common access patterns, and constraints that prevent accidental cross-tenant relationships. Repository functions should require tenant context rather than accepting an optional filter.
For high-assurance systems, combine application checks with database row-level security or separate schemas/accounts where the risk and operating model justify it. Defense in depth is valuable, but it does not replace clear ownership and tests.
Separate Authentication from Authorization
Authentication answers who the caller is. Authorization answers whether that identity can perform this action on this resource in this tenant. Model roles, permissions, resource ownership, and state transitions explicitly.
For example, “editor” may edit a draft but not publish it; “billing admin” may update payment settings but not read private documents. Avoid a single isAdmin boolean when the product has multiple resource types and sensitive actions.
Secure Caches, Search, and Files
Cache keys must include tenant and authorization context. Search indexes must apply tenant filters before returning results. Object storage paths should not be treated as authorization; issue short-lived, scoped URLs only after checking access.
Test cache hits after switching users, reused search queries, signed URLs, exports, and pagination cursors. These paths frequently bypass the normal controller assumptions.
Background Jobs Need Context
A job payload should include a tenant identifier, actor or system principal, authorization-relevant parameters, and an idempotency key. The worker must re-check that the resource still exists and is still allowed before performing the action.
Do not place unrestricted objects or secrets in a queue. Treat queued messages as durable data with its own retention, access control, and audit requirements.
Audit Decisions, Not Just Requests
An audit trail should answer who acted, for which tenant, on what resource, when, from where, what changed, and whether the action succeeded. Record authorization failures and privileged support access as well as successful mutations.
Protect audit records from ordinary application updates. Use append-oriented storage, restricted access, retention rules, and a clear policy for sensitive fields.
Test Isolation as a Matrix
Create fixtures for two tenants with similar IDs and similar records. Test every endpoint as owner, same-tenant non-owner, other-tenant user, revoked user, support user, and unauthenticated caller. Repeat the matrix for jobs, exports, search, files, and caches.
Add a regression test for every discovered authorization bug. Security tests should run in CI and against a deployed staging environment where configuration and proxies are real.
Launch Checklist
- Tenant context comes from a trusted, verified source.
- Repositories require tenant scope by construction.
- Authorization covers actions and resource state.
- Caches, search, files, jobs, and exports preserve isolation.
- Privileged access is explicit, limited, and audited.
- Audit records cannot be silently rewritten.
- Cross-tenant tests run for every sensitive path.
- Rate limits and alerts cover abusive access patterns.
Choosing an Isolation Model
Shared tables with a tenant column are operationally efficient and work well when queries, constraints, and authorization are rigorously designed. Separate schemas provide stronger boundaries but increase migration and connection-management complexity. Separate databases or accounts provide the strongest isolation and are appropriate for regulated workloads or customers with contractual separation requirements.
Choose based on risk, scale, operational capacity, reporting needs, and customer commitments. Do not promise “logical isolation” without explaining how backups, logs, search indexes, files, caches, and support tooling preserve that boundary.
Safe Query Patterns
Prefer repository methods that make the tenant mandatory:
async function findInvoice(ctx: TenantContext, invoiceId: string) {
return db.invoice.findFirst({
where: { id: invoiceId, tenantId: ctx.tenantId },
});
}
Returning null for a resource outside the caller’s tenant is often safer than revealing that it exists. For mutation endpoints, check the resource and authorization in one transaction when concurrent changes could create a race.
Caches and Background Work
A cache key such as invoice:${id} is unsafe in a shared multi-tenant system. Include tenant, authorization-relevant version, locale, and any other value that changes the response. When permissions change, invalidate or version cached results.
For jobs, pass only the identifiers and context needed to re-load the resource. At execution time, re-check tenant ownership and current permissions. A job created before a user was removed must not continue with the old authorization assumption.
Support and Administrative Access
Support staff may need to diagnose a tenant issue, but “support” must not mean unlimited silent access. Require an explicit reason, time-limited elevation, customer or manager approval where appropriate, and an audit record. Mask secrets and sensitive fields by default.
Testing Matrix
Create fixtures for two tenants with similar IDs and similar records. Test every endpoint as owner, same-tenant non-owner, other-tenant user, revoked user, support user, and unauthenticated caller. Repeat the matrix for jobs, exports, search, files, and caches.
Launch Checklist
- Tenant context comes from a trusted, verified source.
- Repositories require tenant scope by construction.
- Authorization covers actions and resource state.
- Caches, search, files, jobs, and exports preserve isolation.
- Privileged access is explicit, limited, and audited.
- Audit records cannot be silently rewritten.
- Cross-tenant tests run for every sensitive path.
- Rate limits and alerts cover abusive access patterns.
For SaaS teams building secure, scalable application foundations, our backend development and API services include authorization design, data modeling, and production hardening.

