Multi-Tenant Authorization Patterns for B2B SaaS
Tenant isolation is the foundation of B2B SaaS security. We cover scope-per-tenant modeling, shared vs. per-tenant roles, safe cross-tenant support access, and the isolation pitfalls that turn into CVEs.
In B2B SaaS, the worst possible bug is one tenant seeing another tenant’s data. Multi-tenant authorization is the discipline of guaranteeing isolation while still supporting shared roles, cross-tenant support access, and per-customer customization. Here are the patterns that hold up in production.
The Isolation Problem
Every authorization decision must be answered within a tenant boundary. “Can this user delete invoice 123?” is meaningless without “in which tenant?” The tenant must be an explicit, non-optional input to every check — never inferred from the resource alone, which is how cross-tenant leaks happen.
Pattern 1: Scope-Per-Tenant
Model each tenant as a scope — a hard boundary that namespaces all roles, permissions, and policies. Access checks always carry the scope, and data keys are prefixed by it:
// The scope is a required parameter — isolation by construction.
await pdp.hasAccess({
subjectId: user.id,
scopeId: 'tenant-acme', // never optional
resource: 'invoice',
action: 'delete',
})This maps directly to a single-table key design where every item lives under SCOPE#{tenantId}. See scopes.
Pattern 2: Shared vs. Per-Tenant Roles
Most SaaS products want a common set of roles (Admin/Member/Viewer) across all tenants, but let some enterprise customers define custom roles. Support both by seeding a default role template into each new scope, while allowing per-scope additions:
- Shared template: seed Admin/Member/Viewer when a tenant scope is created.
- Per-tenant custom: enterprise customers add roles inside their own scope — invisible to others.
Pattern 3: Cross-Tenant Support Access
Your support team occasionally needs into a customer’s workspace. Do not give them a god-mode key. Instead, grant time-boxed, audited, per-scope access — and log every action. Cross-tenant access should be the rare, loud exception, not a quiet default.
Isolation Pitfalls
| Pitfall | Consequence | Mitigation |
|---|---|---|
| Scope inferred from resource | Cross-tenant data leak | Require scope as an explicit parameter |
| Global admin roles | One compromised key exposes all tenants | Scope every role; time-box support access |
| Shared role IDs across tenants | Edits bleed between customers | Namespace roles under the scope |
| No per-tenant audit | Cannot answer "who accessed my data" | Log every decision with scope + subject |
How WardenAuth Models Tenancy
Scopes are first-class in WardenAuth: every permission, role, and policy is namespaced under a scope, and the scope is a required input to every access check — isolation by construction, not by convention. Read the Next.js + Cognito implementation guide or start free.