
Tenant Data Isolation: Patterns and Anti-Patterns
Explore effective patterns and pitfalls of tenant data isolation in multi-tenant systems to enhance security and compliance.
Jul 30, 2025
Read More

Authentication is solved. Authorization is where teams get hurt.
Most SaaS products bolt on Auth0 or Firebase Auth and call it done. Then six months later they're debugging why a team member can delete billing data, why a viewer-role user can invite new members, or why a tenant's admin can see another tenant's records. These aren't edge cases — they're what happens when you treat authorization as an afterthought.
This guide covers OAuth 2.0 as your authentication layer, RBAC (role-based access control) as your authorization strategy, and where ABAC (attribute-based access control) makes sense as you scale.
OAuth 2.0 is an authorization framework, not an authentication protocol. It defines how a user delegates limited access to a third party without sharing credentials. In SaaS, you use it for:
For user authentication specifically, OIDC builds on OAuth 2.0 by adding an id_token (JWT) containing user identity claims alongside the access token.
client_id, scope, redirect_uri, and state.redirect_uri with an authorization code.access_token + id_token server-side. Never expose this exchange to the client.id_token signature against Google's public keys (JWKS endpoint).The state parameter is critical — always verify it on callback to prevent CSRF attacks.
RBAC assigns permissions to roles, and roles to users. Rather than granting user A permission to do X directly, you define a role that has permission X, then assign that role to user A.
projects:delete, billing:read).| Role | Typical Permissions |
|---|---|
| Owner | All permissions including billing and team management |
| Admin | All except billing and ownership transfer |
| Member/Editor | Create, read, update within their scope |
| Viewer | Read-only access |
| Billing Admin | Billing-only, no product data |
Start with 3–4 roles. Expand only when real customer use cases demand it. Most SaaS products never need more than 6 distinct roles.
In multi-tenant SaaS, roles are always scoped to a tenant. A user who is an admin in Workspace A should have zero elevated permissions in Workspace B.
The naive implementation stores roles in a single users table column. This breaks immediately in multi-tenant contexts. The correct approach:
-- User can have different roles in different workspaces
CREATE TABLE workspace_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id),
user_id UUID NOT NULL REFERENCES users(id),
role VARCHAR(50) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(workspace_id, user_id)
);
Every authorization check must include workspace_id as a filter, resolved from server-side context (subdomain, header, or JWT claim).
Centralize permission logic rather than scattering role checks throughout your codebase.
const PERMISSIONS = {
'projects:create': ['owner', 'admin', 'member'],
'projects:delete': ['owner', 'admin'],
'billing:manage': ['owner'],
'members:invite': ['owner', 'admin'],
} as const;
function can(userRole: string, permission: keyof typeof PERMISSIONS): boolean {
return PERMISSIONS[permission].includes(userRole);
}
For API routes, enforce at the route level via middleware:
router.delete('/projects/:id',
requirePermission('projects:delete'),
deleteProjectHandler
);
RBAC breaks down when permissions depend on attributes beyond role — the resource's owner, the user's plan, the data classification level. ABAC (attribute-based access control) evaluates policy expressions against subject, resource, and environment attributes:
Libraries like Casbin and Open Policy Agent implement ABAC with declarative policy files. Introduce ABAC when you find yourself adding more than 2–3 custom conditions to your RBAC permission checks.
workspace_id and role in the JWT to avoid DB lookups on every request.| Pitfall | Fix |
|---|---|
| Role stored globally, not per-workspace | Scope roles to workspace in the workspace_members table |
| Permission checks scattered in business logic | Centralize in middleware or a dedicated authz service |
| No audit log for permission changes | Log all role assignments with actor, timestamp, reason |
| Trust user-supplied workspace_id | Resolve workspace from server-side context only |
| No token revocation on logout | Use short expiry + refresh rotation or maintain a server-side blacklist |
For the full data isolation architecture that RBAC operates within, see Tenant Data Isolation Patterns and Anti-Patterns. For API security beyond auth, see How to Secure API Backends Against MITM Attacks.
OAuth 2.0 handles authentication — verifying who the user is. RBAC handles authorization — determining what that user is allowed to do. They complement each other: OAuth gets you the verified identity, RBAC controls resource access based on that identity's role.
RBAC (role-based access control) assigns permissions to roles (Admin, Member, Viewer) and assigns those roles to users within a workspace. It's simpler to manage than per-user permission grants and scales cleanly for most B2B SaaS use cases.
Use ABAC when permission decisions depend on resource attributes beyond the user's role — such as resource ownership, subscription tier, data classification, or environmental factors like IP address. RBAC + ABAC together is the enterprise-grade standard.
Scope all roles to a workspace or tenant in a dedicated membership table. Every database query must include the workspace_id resolved from server-side context — never from user-supplied input. Never store roles in a global user record.
Need an expert team to provide digital solutions for your business?
Book A Free CallDive into a wealth of knowledge with our unique articles and resources. Stay informed about the latest trends and best practices in the tech industry.
View All articlesTell us about your vision. We'll respond within 24 hours with a free AI-powered estimate.
© 2026 Propelius Technologies. All rights reserved.