patterntypescriptCritical
Multitenancy in APIs: Tenant isolation via middleware and query scoping
Viewed 0 times
multitenancytenant-isolationrow-level-securitysaasscopingjwt
Problem
In multi-tenant SaaS APIs, failing to scope every database query to the current tenant is a critical data isolation bug. A single missing WHERE tenant_id = ? clause can expose one tenant's data to another.
Solution
Extract the tenant identifier in middleware from JWT claims or URL path (/api/orgs/:orgId). Inject it into the request context. Use a database abstraction layer that automatically appends the tenant scope to every query, rather than relying on developers to add it manually.
Why
Manual tenancy scoping relies on developer discipline, which fails at scale. Automatic scoping at the data access layer makes cross-tenant data leakage structurally impossible rather than convention-dependent.
Gotchas
- Ensure tenant ID comes from the authenticated session/JWT, not a user-supplied header or query param.
- Background jobs must also scope to a tenant — they do not have a request context.
- Super-admin cross-tenant access must be an explicit opt-in with audit logging, never the default.
Code Snippets
Automatic tenant scoping in a repository layer
class TenantScopedRepository<T> {
constructor(
private db: Database,
private table: string,
private tenantId: string
) {}
async findAll(where?: Partial<T>): Promise<T[]> {
// tenantId is always appended — cannot be bypassed
return this.db
.from(this.table)
.where({ tenant_id: this.tenantId, ...where })
}
async findById(id: string): Promise<T | null> {
return this.db
.from(this.table)
.where({ tenant_id: this.tenantId, id })
.first()
}
}Revisions (0)
No revisions yet.