Most SaaS products should start with tenant-scoped RBAC, use ABAC for contextual restrictions, and add relationship-based access control when permissions depend on ownership, sharing, groups, inheritance, or hierarchies. In a production multi-tenant SaaS platform, a role by itself is not a safe authorization decision. Reliable enforcement has to consider the authenticated actor, the active tenant, membership state, the requested action, the target resource, the resource’s authoritative tenant owner, role permissions, relevant attributes and relationships, commercial entitlements, and the current security context. AWS explicitly treats tenant isolation as a separate architectural concern from authentication and ordinary authorization; OWASP similarly recommends validating permissions on every request and warns against trusting tenant identifiers supplied by clients.
That distinction matters because the typical SaaS journey looks deceptively simple at first. An MVP launches with Admin, Member, and Viewer. Then customers ask for custom roles. Some users join multiple organizations. Project-level access appears. Enterprise buyers ask for SSO, SCIM, delegated administration, and auditable support access. Authorization checks spread across controllers, services, background jobs, exports, storage paths, and search indices. At that point, “user management” stops being a UI feature and becomes an architecture problem. NIST’s RBAC model, NIST’s ABAC guidance, OWASP’s authorization recommendations, and Google’s Zanzibar paper all point in the same direction: access control becomes more reliable when it is modeled explicitly, centralized where practical, and evaluated against the right inputs every time.
When User Management Becomes an Architecture Problem
Authentication, authorization, tenant isolation, and entitlements are often discussed as if they were interchangeable. They are not. OWASP defines authorization as verifying whether a requested action is approved for a specific entity, while separately defining authentication as verification of identity. AWS states that tenant isolation focuses specifically on using tenant context to limit access to resources, and Azure notes that tenant mapping is a separate concern from user-based permissions. SCIM, meanwhile, standardizes identity provisioning and lifecycle exchange rather than runtime authorization decisions.
| Concern | The question it answers | SaaS example | Typical component |
|---|---|---|---|
| Authentication | Who is this actor? | “This request is from Anna, authenticated with the company IdP.” | IdP, OpenID Connect provider, session service |
| Authorization | Can this actor perform this action on this resource? | “Anna may update project p_123.” | Application policy engine, authorization service, middleware |
| Tenant isolation | Is this resource inside the actor’s currently authorized tenant boundary? | “Anna is in Tenant A, but invoice_99 belongs to Tenant B, so deny.” | Tenant context resolver, repository filters, database RLS, storage scoping |
| User management | How are identities, memberships, invitations, lifecycle events, and access administered? | Invite user, assign membership, suspend guest, map IdP groups | Admin UI, identity sync, SCIM service, membership service |
| Entitlements | Is this tenant commercially entitled to this feature or limit? | “Tenant plan includes SCIM but not advanced exports.” | Billing/plan service, entitlement service |
The table above reflects OWASP’s distinction between authentication and authorization, AWS’s framing of tenant isolation as its own access-control concern, Azure’s separation of tenant mapping from user permissions, and the SCIM standards’ treatment of provisioning as a lifecycle protocol rather than a runtime PDP.
This is why a correctly authenticated administrator can still become a security incident. Suppose the application checks that the caller is an Admin, accepts a client-supplied tenant_id, and then looks up invoice_id=99. If the code fails to verify that invoice_99.tenant_id belongs to the active tenant and that the actor has valid membership in that tenant, the administrator may access another customer’s invoice. AWS makes this explicit: tenant isolation is separate from basic authentication and authorization, and a user can be authenticated and authorized yet still reach another tenant’s resources if isolation is not enforced. OWASP’s top API risk for 2023 similarly warns that every function accessing a data source by user-supplied identifiers needs object-level authorization checks.
In large SaaS systems, the problem gets broader than API handlers. The same decision model must hold in REST and GraphQL endpoints, background jobs, search, exports, object storage, analytics extracts, webhooks, support tools, and AI or RAG features. Zanzibar’s design notes that a unified authorization system makes it easier to preserve consistent semantics across applications and even build shared infrastructure such as search indices that respect access control. That is the architectural standard modern SaaS teams should aim for, even if they implement it in stages.
Designing the SaaS Permission Model
A sound SaaS user management model starts with the right nouns.
A user is the human or machine actor represented in your product.
An identity is the authenticated representation of that actor in your IdP or identity layer.
A tenant is the customer boundary.
A membership is the actor’s relationship to a tenant.
A role is a named bundle of permissions.
A permission is a stable capability such as project.update.
An action is the operation being requested.
A resource is the target object.
A scope describes whether a permission applies tenant-wide or to a specific resource set.
A group represents a collection of users or memberships.
An attribute is descriptive data about a subject, resource, action, or environment.
A relationship captures links such as owner, editor, approver, parent folder, or team member.
A policy is the rule set that turns those facts into an allow or deny decision.
An entitlement is commercial availability, such as whether the tenant’s plan unlocks SSO, SCIM, or export APIs.
These concepts are directly aligned with NIST RBAC and ABAC definitions, Cedar’s principal-action-resource-context model, OpenFGA’s typed relations, and SCIM’s standardized user, group, role, and entitlement vocabulary.
Roles in multi-tenant SaaS should normally be assigned to tenant memberships, not directly to global user records. A global user may belong to several tenants, each with different responsibilities, restrictions, and lifecycle state. Azure explicitly warns that tenant-specific information in a shared user record can leak across tenants, and AWS frames the binding between user identity and tenant context as a first-class SaaS construct rather than an afterthought lookup.
The baseline model is straightforward:
User → Identity → Membership → Tenant → Role → Permissions
For fine-grained access, extend it like this:
Membership → Group / Relationship / ResourceGrant → Resource
That extension is where most real products mature. A finance manager may approve invoices for one subsidiary but only view invoices elsewhere. A contractor may access one workspace for 30 days. A support engineer may open a time-boxed, audited session into a customer tenant without becoming a tenant admin. A viewer may read a project because it was shared with their team, not because their tenant-wide role grants every project permission. NIST’s ABAC model explicitly includes subject, object, requested operation, environment conditions, and relationships in authorization decisions, which is why a pure role-only model becomes brittle as SaaS products scale.
RBAC as the SaaS baseline
RBAC remains the right starting point for most SaaS teams because it turns a long permission list into understandable responsibility packages. NIST’s RBAC standard defines users, roles, permissions, operations, objects, and role hierarchies, and the broader NIST RBAC model includes hierarchical and constrained forms of RBAC. In practice, tenant-scoped roles such as Owner, Admin, Billing Admin, Security Admin, Manager, Editor, Viewer, and Auditor can cover a large share of early and mid-stage requirements with relatively low operational overhead.
A practical production pattern is to keep a small set of immutable system roles for platform-critical tasks, then allow customer-defined custom roles as permission bundles within the tenant boundary. System roles usually cover responsibilities that must always exist or must retain safety guarantees, such as Owner, Billing Admin, or perhaps Security Admin. Custom roles then inherit from a stable permission vocabulary rather than from ad hoc boolean flags in tables or UI forms. That approach stays closer to NIST RBAC’s formal model while reducing the risk of accidental privilege creation when the product surface expands.
Role hierarchies and separation of duties are useful, but they need discipline. A hierarchy might let Admin imply Manager, Editor, and Viewer permissions. A separation-of-duties rule might prevent the same membership from both configuring SSO and approving invoice payouts, or from both creating vendors and releasing payments. NIST’s constrained RBAC work exists precisely because combinations of roles can create business risk even when every individual permission looks legitimate in isolation.
RBAC is usually sufficient when the decision is mostly about responsibility inside a single tenant and the objects of that responsibility are broad. It works well for tenant administration, billing pages, security settings, audit viewing, CRUD access to ordinary business objects, and well-bounded back-office tasks. It starts to strain when access depends on thresholds, geography, time windows, ownership chains, temporary support access, parent-child resource trees, or object sharing. That is where teams experience “role explosion”: every new exception becomes pressure to add still more roles. OWASP’s guidance to prefer attribute- and relationship-based access control for richer scenarios is best read not as “throw away RBAC,” but as “do not make RBAC carry the full weight of a modern product.”
ABAC for contextual restrictions
NIST defines ABAC as a logical access-control methodology in which authorization is determined by evaluating attributes associated with the subject, object, requested operation, and possibly environment conditions against policy. That maps cleanly to SaaS products because many of the hard cases are contextual, not organizational.
Subject attributes might include department, title, employment type, contractor status, device trust state, MFA strength, support clearance, or guest expiration. Resource attributes might include data classification, region, currency, invoice amount, project sensitivity, environment, or owner department. Action attributes are usually simpler but still matter; approving an invoice is not the same as viewing it. Environment attributes include time of day, IP range, country, session risk score, device posture, or whether step-up authentication has just occurred. Cedar’s model expresses these decisions as principal, action, resource, and context, and its documentation explicitly shows context containing transient facts such as IP address and MFA state.
That makes ABAC ideal for restrictions such as these:
- a finance approver may approve invoices only up to a threshold;
- an admin may configure security settings only after MFA step-up;
- HR records may be readable only by employees in the same legal entity or department;
- support access is allowed only during a ticket window and from managed devices;
- exports containing regulated data are allowed only for tenants entitled to that feature and only in specific regions.
These examples are not vendor fantasies; they are the normal shape of enterprise SaaS requirements once simple CRUD roles are no longer enough. NIST’s ABAC guidance, Cedar’s examples, and OWASP’s preference for richer models all support this pattern.
ABAC does introduce real engineering costs. Attribute freshness matters: an outdated department value, device-trust flag, or support-window timestamp can turn a correct policy into a wrong decision. Explainability becomes harder because the answer is now the product of multiple facts, not just a role label. Debugging also gets trickier when allow or deny depends on condition evaluation rather than on a static permission matrix. Cedar’s authorizer returns diagnostics and a default deny unless a permit rule matches without any forbid rule matching; OPA can emit decision logs that include the queried policy, the input, and metadata for auditing and offline debugging. Those features are useful precisely because ABAC increases expressiveness and therefore the need for policy observability.
So the best practical recommendation for most products is not “ABAC instead of RBAC.” It is:
RBAC baseline + ABAC constraints
RBAC answers who broadly could do something in a tenant. ABAC answers whether this specific request, in this specific context, should still be allowed. That combination preserves clarity for administrators while keeping engineers out of the trap of creating one role per edge case.
Tenant-level permissions are the real center of gravity
In a multi-tenant product, the most important authorization input is usually not the user, but the tuple of actor + active tenant + target resource. Azure says tenant mapping is a separate activity from user permissions, and asks a critical design question: if a user can be a member of multiple tenants, how will they select the tenant they want to work with for a specific request? AWS similarly describes SaaS identity as the binding of user and tenant context that needs to flow through the platform.
Tenant-level permissions therefore require at least the following checks on the server side:
- resolve the active tenant context for the request;
- verify the actor has a valid membership in that tenant;
- confirm membership state is active and not suspended or expired;
- load the target resource from an authoritative source;
- verify the resource belongs to the same tenant boundary or a deliberately allowed related boundary;
- evaluate role permissions for that membership;
- apply ABAC and relationship rules;
- verify the tenant is entitled to the requested feature.
OWASP’s multi-tenant guidance says to establish tenant context early, bind it to authenticated sessions, propagate it securely through application layers, and never trust client-supplied tenant IDs without validation. Microsoft’s guidance on multi-tenant misconfiguration and Azure SDK guidance add another crucial point: multi-tenant apps must validate resource ownership, because accepting user-provided resource identifiers or URIs without ownership checks can produce unauthorized cross-tenant access.
That is why a tenant ID from the client is never authoritative on its own. It can be a hint for selecting context, but not proof of authorization. If the browser sends tenant_id=acme, the server still has to verify that the authenticated actor has a valid membership in acme, that the request is permitted in that tenant, and that the resource being accessed is owned by acme or by a specifically allowed child or linked entity. Azure’s guidance on validating resource ownership and AWS’s explicit separation of isolation from basic authentication both support this rule.
This also affects more complex B2B structures. Many products need organizations, workspaces, projects, business units, subsidiaries, franchise trees, resellers, or customer-managed portfolios. The right approach is usually to distinguish platform roles from tenant roles. A platform support engineer or internal compliance analyst may have access to internal tooling without becoming part of a customer tenant. A reseller admin may manage several downstream tenants but not inherit unrestricted access to every resource in each one. Guests and contractors should generally be implemented as memberships with explicit scope, expiry, and auditing, not as diluted full users.
Temporary internal support access deserves special treatment. It should be time-boxed, ticket-linked, audited, and preferably mediated by a dedicated support-access workflow rather than by hidden super-admin privileges. The authorization system should record not only that support access was used, but why, by whom, for which tenant, and under what approval or break-glass rule. OWASP’s emphasis on auditing, validating every request, and including tenant context in logs makes this the safer default.
Permissions, entitlements, and resource policies are different things
These three are often mixed together in SaaS products, and that confusion causes bugs and billing disputes.
A permission is what a membership is allowed to do. Example: report.export.
An entitlement is whether the tenant has purchased or been provisioned for a feature. Example: the Growth plan includes CSV exports but not API bulk export or SCIM.
A resource policy is a rule attached to or derived from a resource or its relationships. Example: project owners may share this project with another team, but only Viewer access is inheritable through the parent folder.
SCIM’s core schema explicitly includes entitlements and roles as distinct user attributes, and says entitlement vocabulary is service-provider defined. That is useful because it reminds SaaS architects that “what the customer bought,” “what the user’s role allows,” and “what this resource’s relationships allow” are all separate dimensions.
An export feature shows the difference clearly. A membership might have the permission report.export. The tenant might still lack the commercial entitlement advanced_exports. And even if both are true, a resource policy or ABAC rule might still deny export of a regulated dataset unless the data region and authentication context meet stricter conditions. Treating all three checks as one boolean flag is how enterprise access control drifts into inconsistent behavior.

RBAC, ABAC, and ReBAC belong together
| Model | Decision basis | Best SaaS use cases | Main benefits | Main limitations | Relative complexity |
|---|---|---|---|---|---|
| RBAC | Role membership and permission bundles | Tenant administration, billing, standard CRUD, operational delegation | Simple mental model, easy admin UX, good auditability | Weak for exceptions, sharing, ownership chains, and context-heavy rules | Lower |
| ABAC | Subject, resource, action, and environment attributes | Approval thresholds, region rules, device trust, MFA step-up, temporary access | Fine-grained, context-aware, expressive | Harder debugging, freshness issues, more policy inputs | Medium |
| ReBAC | Relationships among users, groups, and resources | Document sharing, folders, teams, ownership, project graphs, inherited access | Natural fit for collaboration and hierarchy | Requires graph modeling, careful consistency and recursion handling | Medium to higher |
NIST defines RBAC and ABAC distinctly, OWASP explicitly calls out RBAC, ABAC, and ReBAC as core access-control options, and Zanzibar demonstrates how relationship tuples can model owners, editors, commenters, viewers, groups, and nested hierarchies at enormous scale. OpenFGA and SpiceDB both expose Zanzibar-inspired approaches for teams that need this kind of fine-grained sharing model.
ReBAC becomes useful when access depends on who is related to what, not merely on “what job title does this person have.” Shared documents, nested folders, private projects, team membership, delegated approvers, organization hierarchies, and “viewer if you can view the parent container” are classic ReBAC cases. Zanzibar’s model treats ACLs as relations such as owner, editor, commenter, and viewer, and supports nested groups and usersets. OpenFGA’s authorization model and relationship tuples, and SpiceDB’s Zanzibar-inspired schema, are direct examples of how these ideas are packaged for application teams.
The right recommendation for most production SaaS platforms is therefore a hybrid:
- RBAC for responsibilities
- ABAC for contextual restrictions
- ReBAC for ownership, sharing, groups, and hierarchies
- tenant boundaries for isolation
- entitlements for commercial availability
That is more operationally realistic than arguing for a single “best” model. It also aligns much better with how products actually evolve.
Reference Architecture for Multi-Tenant Authorization
A practical multi-tenant authorization architecture usually contains the following components: an external identity provider, an API gateway or edge layer, a tenant-context resolver, one or more Policy Enforcement Points, a Policy Decision Point, a policy or relationship store, the application database, an entitlement service, an audit log, and a cache or policy distribution layer. AWS’s SaaS architecture guidance separates control-plane identity services from application-plane tenant functionality, while Azure emphasizes tenant mapping and authorization as distinct concerns. OPA, Cedar, OpenFGA, SpiceDB, and Amazon Verified Permissions illustrate different implementation styles for the PDP and policy store parts of this architecture.
A compact authorization request should carry the inputs the decision actually needs, not just a user ID and a role string.
json{
"actor": {
"id": "user_123",
"type": "human",
"authn": {
"amr": ["pwd", "mfa"],
"session_id": "sess_789"
}
},
"tenant": {
"active_tenant_id": "tenant_acme",
"membership_id": "m_456",
"membership_status": "active"
},
"action": "invoice.approve",
"resource": {
"type": "invoice",
"id": "inv_9001",
"tenant_id": "tenant_acme",
"owner_id": "project_finance"
},
"attributes": {
"invoice_amount": 12500,
"department": "finance",
"region": "eu-west-1"
},
"relationships": {
"is_project_approver": true,
"is_invoice_owner": false
},
"entitlements": {
"advanced_approvals": true
},
"security_context": {
"ip_trusted": true,
"device_managed": true,
"step_up_mfa": true,
"request_time": "2026-07-11T10:13:00Z"
}
}
That request shape is consistent with Cedar’s principal-action-resource-context model, OPA’s structured JSON input model, NIST ABAC’s subject-object-operation-environment framing, and AWS’s guidance that tenant context should flow through services as part of the application’s identity representation.
A reference decision flow for SaaS authorization architecture looks like this:
Authenticate → Resolve active tenant → Confirm membership and status → Load resource → Verify resource tenant ownership → Enforce tenant boundary → Check entitlements → Evaluate roles → Evaluate attributes and relationships → Return allow/deny with reason → Write audit event
This flow is not ceremony. It is the minimum path required to keep responsibilities, context, and isolation from collapsing into one another. AWS explicitly states that authentication and ordinary authorization do not automatically produce tenant isolation. OWASP recommends validating permissions on every request. Microsoft’s multi-tenant guidance adds that resource ownership must be validated to prevent cross-tenant leaks.
The enforcement points need to exist everywhere the product can move or reveal data. In REST APIs, object-level and function-level checks must happen before data access. In GraphQL, field and resolver-level authorization becomes important because one query can traverse many object types and properties. In background jobs, the job payload must carry or resolve authoritative tenant context rather than inheriting ambient execution privileges. In queues and async workers, tenant context should be explicit and immutable for each message. In exports and analytics, scope must be resolved before assembling records, not after building a broad dataset. In search and AI assistants, indices and retrieval layers need the same permission semantics as the source systems; Zanzibar specifically calls out the value of shared infrastructure such as search that respects access control.
Defense in depth is what turns that flow into a resilient platform. Application-layer authorization prevents most mistakes at the business-logic level. Database row-level security can reinforce tenant boundaries closer to data. PostgreSQL documents that once row security is enabled, normal access must be allowed by policy, and if no policy exists then a default-deny policy applies; it also warns that superusers and BYPASSRLS roles can bypass protections unless handled carefully. OWASP’s multi-tenant guidance recommends row-level isolation strategies, tenant-aware cache keys, isolated file paths, and tenant-rich logging.
Some teams try to rely entirely on database isolation, while others rely entirely on application checks. In practice, the safer pattern is layered enforcement. Use application checks for semantics and intent, database policies for row scoping, tenant-aware cache keys for shared caches, tenant-prefixed or isolated storage paths for object storage, scoped cloud credentials for downstream services, and audit logs that always include tenant context. AWS and OWASP both frame tenant isolation as a system-wide responsibility across layers, not a property of a single middleware function.
Policy engines fit into this architecture in different ways. OPA externalizes decisions from business code and can be embedded or called sidecar-style. Cedar separates policies from application code and evaluates principal-action-resource-context requests, returning decisions with diagnostics. Amazon Verified Permissions adds managed policy storage, schema validation, and centralized administration using Cedar. OpenFGA and SpiceDB are better fits when resource relationships are a first-class part of the product model. None of these tools removes the need to supply the correct tenant, membership, resource ownership, and entitlement inputs. Externalized authorization only helps if the data passed to it is authoritative.
Data Model, Permission Vocabulary, and Example Permission Sets
A durable SaaS permissions model needs a stable vocabulary at the data layer. NIST’s RBAC standard is still useful here because it formalizes the elements and relations rather than a specific user interface. OpenFGA and Zanzibar reinforce the same lesson on the relationship side: authorization works better when the model has named types, named relations, and explicit tuples instead of scattered booleans.
A practical conceptual model often includes these tables or aggregates:
| Entity | Purpose |
|---|---|
users | Global actor record independent of any one tenant |
tenants | Customer organizations, workspaces, business units, or accounts |
memberships | User-to-tenant link with status, dates, and metadata |
roles | Tenant-scoped role definitions, including system and custom roles |
permissions | Stable permission identifiers such as project.read |
role_permissions | Mapping between roles and permissions |
membership_roles | Assignment of one or more roles to a membership |
groups | Membership collections for team, department, or project access |
resource_grants | Per-resource access grants or overrides |
policies | ABAC or policy-as-code rules, or references to external policy objects |
audit_events | Authorization decisions, changes, support access, and admin actions |
This model is synthesized from NIST RBAC elements, NIST ABAC inputs, SCIM’s user and group resources, and Zanzibar-style relation tuples. It is deliberately tenant-aware because a single global role on the user record is insufficient for multi-tenant SaaS.
The permission vocabulary should use stable machine identifiers, not display labels. Good examples include project.read, project.update, invoice.approve, member.invite, and security.sso.configure. Stable identifiers matter because they survive localization, UI redesigns, mergers of roles, and custom-role migration. They also let you version permissions safely. If an old permission is being retired, keep its identifier mapped long enough to migrate roles and audit trails cleanly instead of silently reinterpreting it. That is a practical extension of OWASP’s deny-by-default and explicit-justification guidance.
Tenant-wide scope and resource-level scope should be modeled separately. project.read may mean “read any project in this tenant” when bound to a tenant-admin role, while a relationship grant may mean “read project p_42 because the actor is a collaborator.” Inheritance rules should be explicit, not implied by naming. If a workspace grant implies access to all contained projects, write that inheritance rule down in the model and test it. If an Auditor can read logs but cannot export them, encode that with distinct permissions rather than UI filtering. OWASP’s API guidance on object-level and property-level authorization is essentially a warning not to assume that broad object access automatically implies safe field-level or action-level access.
Deny-by-default needs to be real, not rhetorical. OWASP says applications should deny by default, and PostgreSQL’s row-security model likewise falls back to default deny when RLS is enabled but no policy exists. The same philosophy should govern your permission schema. New resources, new actions, and new endpoints should remain inaccessible until a role, policy, or relationship explicitly allows them. Wildcard permissions such as *.* or admin.* may feel convenient early on, but they become dangerous during product expansion because they inherit access to future capabilities the original admin never intended.
An illustrative role and permission matrix might look like this:
| Permission | Owner | Admin | Billing Admin | Security Admin | Manager | Editor | Viewer | Auditor |
|---|---|---|---|---|---|---|---|---|
tenant.settings.update | ✓ | ✓ | ✓ | |||||
member.invite | ✓ | ✓ | ✓ | |||||
member.role.assign | ✓ | ✓ | ||||||
billing.invoice.read | ✓ | ✓ | ✓ | ✓ | ||||
billing.payment_method.update | ✓ | ✓ | ||||||
security.sso.configure | ✓ | ✓ | ||||||
security.scim.configure | ✓ | ✓ | ||||||
project.read | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
project.update | ✓ | ✓ | ✓ | ✓ | ||||
invoice.approve | ✓ | ✓ | ✓ | |||||
audit.read | ✓ | ✓ | ✓ | ✓ | ||||
report.export | ✓ | ✓ | ✓ | ✓ |
This matrix is illustrative rather than normative, but it follows least-privilege principles from OWASP and the role/permission abstraction from NIST RBAC. In a real product, some of these permissions would further require ABAC or relationship checks. For example, invoice.approve might still need amount thresholds and legal-entity attributes, and report.export might still depend on plan entitlements and data-governance rules.
Safe custom-role migration deserves extra attention. When you add a new permission or split one permission into several, do not silently widen access for existing custom roles. Version the role definition, compute a migration preview, show administrators the delta, record the change in audit logs, and invalidate caches and stale tokens. This is partly an engineering practice and partly a governance practice: explicitness is the only reliable antidote to privilege creep. Externalized systems such as Cedar-based services and Verified Permissions can help because schemas validate policy structure, and invalid policies can be rejected before deployment.
User Lifecycle, Enterprise Integrations, and Tenant-Aware Identity
Enterprise SaaS user management is not just login. It is invitation, verification, membership creation, role assignment, tenant switching, SSO, JIT provisioning, SCIM synchronization, suspension, deprovisioning, revocation, and access review. Azure recommends using an external identity provider rather than building your own IdP, calling the build-your-own approach complex, expensive, and challenging to secure. That advice is especially sound once you need federation, enterprise SSO, lifecycle sync, and tenant-aware claims.
The lifecycle should normally start with a global identity plus one or more memberships. A user can exist without any active membership, and a membership can be suspended or deleted without deleting the global user identity itself. That distinction matters for contractors, users who belong to multiple customer organizations, and people who leave one tenant but remain active in another. Azure explicitly warns that users can have access to multiple tenants and that tenant-specific data in a user record needs careful handling to avoid leakage.
For SSO, OpenID Connect provides the identity layer on top of OAuth 2.0. OAuth 2.0 is delegated authorization, while OpenID Connect is the identity layer that lets the client verify the end user and obtain profile claims. In a SaaS context, that typically means the IdP authenticates the user, the application maps or resolves tenant context, and the platform then performs its own tenant-aware authorization checks. AWS recommends that the identity representation passed downstream include both user and tenant context; Azure notes that tokens can carry claims used for tenant mapping, but tenant selection still needs explicit handling when a user belongs to multiple tenants.
JWTs are useful because they are compact claim containers for transmission in constrained environments, but they should not become your long-lived source of truth for dynamic permissioning. RFC 7519 defines JWT as a compact claims representation format, not a revocation or consistency mechanism. If you stuff full permission sets, tenant state, plan state, and support flags into long-lived tokens, you create unavoidable staleness when roles change, memberships are suspended, or entitlements are updated. That is an architectural inference, but it follows directly from JWT’s self-contained nature and the standards-based need for token/session revocation paths such as OAuth token revocation and OpenID Connect session/logout mechanisms.
That is why a balanced production pattern is usually: keep tokens relatively small, include identity and selected tenant-context claims, and resolve volatile authorization facts from authoritative services or caches with controlled TTLs. If an access token model needs multi-tenant flexibility, Azure’s tenant-mapping guidance and AWS’s SaaS identity guidance are both reminders that tenant context has to be managed deliberately, not inferred casually from email domains or UI state.
SCIM matters once enterprise customers want lifecycle automation. RFC 7644 defines SCIM as an HTTP-based protocol for provisioning and managing identity data across domains, and RFC 7643 defines common schemas for users and groups. That makes SCIM valuable for onboarding, offboarding, and group synchronization, but not a replacement for your runtime authorization engine. SCIM tells your SaaS who exists, whether they are active, and which groups they belong to; your application still has to map those inputs into memberships, roles, and policies safely.
SCIM’s active attribute is particularly useful for suspension workflows. RFC 7643 says active is a boolean indicating the user’s administrative status and gives the typical example that true means the user can log in while false means the account has been suspended. That maps cleanly to SaaS offboarding: when SCIM says a workforce account is no longer active, the application should disable new access promptly and revoke or age out existing sessions, tokens, and API keys according to its security design.
SCIM groups are also valuable but easy to misuse. RFC 7643 defines groups and nested groups, but explicitly leaves the semantics of group membership and any resulting authorization behavior to the service provider. In other words, IdP group mapping is a powerful input, but your product still decides what “Okta group Finance-Approvers” means inside the tenant. That mapping should be explicit, reviewable, and tenant-scoped. A group from one tenant’s IdP integration must never leak meaning into another tenant’s authorization state.
JIT provisioning is convenient, but it must not bypass tenant authorization. A user arriving through SSO can be auto-created as a global identity or even auto-added as a membership if the tenant has configured that behavior, but the application still needs an authoritative rule about which tenant they are joining, which membership state they receive, and which role or group mappings apply. The fact that authentication succeeded is not enough. Azure’s tenant mapping guidance and AWS’s separation of identity context from downstream enforcement both reinforce this point.
Deleting a tenant membership is not the same as deleting the global user identity. In B2B SaaS, a single person can belong to multiple customer tenants. Remove the membership when the relationship to one customer ends; remove the global identity only when the actor should no longer exist in the platform at all. This distinction makes audit history, support interactions, legal holds, and cross-tenant identity hygiene much easier to manage. It is also more consistent with SCIM’s separation of users, groups, and status propagation.
Session and token revocation need equal care. OAuth token revocation defines a standard endpoint for invalidating tokens no longer needed, and OpenID Connect session management defines how relying parties can track login state and logout. In SaaS authorization terms, that means a membership suspension, role downgrade, or support-session expiry should trigger fast invalidation of sessions, refresh tokens, edge caches, and any derived authorization caches wherever practical. API keys deserve the same treatment because they are often longer lived and easier to forget than user sessions. Azure’s guidance explicitly notes that API keys are long-lived, frequently reused, and weaker than short-lived claims-based mechanisms for APIs.
Failure Modes, Testing, and Operational Hardening
Most authorization failures are not caused by choosing the “wrong” model. They happen because the model is only partially enforced. OWASP’s authorization guidance says permissions should be validated on every request, and OWASP’s multi-tenant guidance warns about tenant-context injection, cache poisoning, storage pollution, incomplete offboarding, and insufficient tenant-specific logging. That is the backdrop for the most common SaaS failures.
The first failure is storing one global role on the user record. This happens because early MVPs have a single organization assumption. It goes wrong as soon as one human belongs to multiple tenants or needs different responsibilities in different customer contexts. The fix is to move access control to membership-level roles and tenant-aware grants. Azure’s multitenant identity guidance and AWS’s SaaS identity model both support that separation.
The second failure is trusting tenant IDs from requests. It happens because passing tenant_id in a path, header, or query string feels simple. What goes wrong is tenant-context injection or cross-tenant horizontal escalation. OWASP says never trust client-supplied tenant IDs without validation, and Azure’s guidance on multi-tenant resources says ownership must be validated before accessing customer-controlled resources.
The third failure is frontend-only authorization. Teams hide buttons or routes and assume that prevents access. What goes wrong is direct API invocation. OWASP’s recommendation to validate permissions on every request exists precisely because attackers only need one unguarded backend path.
The fourth failure is checking a role but not checking resource tenant ownership. It often appears in handler code that says “if admin, allow” and then fetches an object by ID. What goes wrong is cross-tenant data exposure despite successful authentication and apparent authorization. AWS explicitly warns that authenticated and authorized users can still reach other tenants’ resources if isolation is not separately enforced, and OWASP API Security calls out broken object-level authorization wherever IDs from the user are used to access data.
The fifth failure is stale JWT permissions. It happens because teams put too much volatile authorization state into self-contained tokens and then rely on token expiry to clean up after access changes. What goes wrong is that suspended memberships, downgraded roles, or support-session expirations remain effective until the token ages out unless revocation and authoritative rechecks exist. This is a systems-design inference grounded in JWT’s self-contained claims model and the existence of standards for revoking tokens and managing sessions.
The sixth failure is insecure list and export endpoints. Teams often secure object detail pages but forget bulk reads. What goes wrong is that a list query, export, or GraphQL query returns rows or fields the user should never see. OWASP’s API Top 10 addresses both object-level authorization and property-level authorization. If you filter after fetching or serialize entire objects with generic methods, you risk overexposure.
The seventh failure is post-filtering unauthorized data. This happens when the system first loads a broad dataset and only later removes forbidden rows or fields. What goes wrong is leakage through caches, logs, exports, timing, pagination counts, or serializer side channels. PostgreSQL’s row-level security design is a useful contrast because unauthorized rows are filtered before normal query processing proceeds.
The eighth failure is missing tenant context in background jobs. It happens when jobs run under service identities with broad access and do not carry the tenant and membership facts that would have gated the original request. What goes wrong is that emails, exports, webhook payloads, analytics jobs, or reconciliation tasks run outside customer boundaries. AWS recommends packaging SaaS identity and tenant context in a way that downstream services can apply without separate lookups, and OWASP recommends propagating tenant context securely through all layers.
The ninth failure is tenant-unaware cache keys. It happens because caches are optimized around object IDs or query shapes alone. What goes wrong is cache collision across tenants, especially for common identifiers such as user_1, dashboard_default, or preferences:123. OWASP’s multi-tenant guidance is explicit: prefix cache keys with tenant identifiers, validate keys, and consider separate namespaces or instances for sensitive workloads.
The tenth failure is shared storage paths without ownership checks. It happens when object storage keys are generated from file names or global object IDs alone. What goes wrong is that one tenant can fetch or overwrite another tenant’s files through signed URLs, predictable paths, or support tooling. OWASP recommends tenant-prefixed paths, storage access policies per tenant, ownership validation before serving files, and signed URLs with tenant context.
The eleventh failure is uncontrolled support impersonation. It happens when internal staff get hidden super-admin access with no time limit, no ticket, and no audit trail. What goes wrong is unreviewable data access and a high-consequence insider path. The fix is temporary, approved, audited support access with explicit tenant context and strong logging. OWASP’s tenant-aware logging guidance gives the right direction here.
The twelfth failure is unsafe custom-role migrations. It happens when new permissions are introduced and existing custom roles are silently remapped. What goes wrong is accidental privilege escalation or unexpected feature loss. Safer migration needs versioned permissions, previewable diffs, and authorization regression tests. OWASP’s authorization testing automation guidance is particularly relevant because it recommends formalizing an authorization matrix and using integration tests to catch drift when releases change access behavior.
The thirteenth failure is wildcard permissions. It happens because they accelerate early development. What goes wrong is privilege sprawl as the product grows: admin.* today may unexpectedly authorize a new enterprise security operation tomorrow. Deny-by-default and explicit permission identifiers are the cure.
The fourteenth failure is inactive memberships retaining access. It happens when deprovisioning updates the user record but not the membership or when background access channels continue working. What goes wrong is lingering access by ex-employees, expired guests, or vendors after the commercial relationship changes. SCIM’s active status, OAuth revocation, and OIDC session/logout support all exist to reduce this lag.
The fifteenth failure is API keys keeping old privileges. It happens because keys are treated as static application credentials. What goes wrong is that authorization changes affecting the human owner or tenant are not reflected quickly in machine access. Azure’s guidance notes the weaker security characteristics of API keys compared with short-lived token-based approaches. If you must use keys, bind them to tenant and scope, rotate aggressively, and re-evaluate privileges server-side.
The sixteenth failure is search, analytics, and RAG ignoring permissions. It happens because these systems are built as “downstream convenience layers” rather than as access-controlled surfaces. What goes wrong is that a seemingly harmless search result, embedding store, or analytics export reveals unauthorized titles, snippets, counts, or full documents. Zanzibar’s paper is instructive here because one of the benefits it cites for unified authorization is the ability to build shared infrastructure such as search that respects access control across applications.
Testing authorization and tenant isolation
A serious testing strategy proves both functionality and isolation. OWASP’s authorization testing automation guidance recommends formalizing an authorization matrix and driving integration tests from it, which is particularly valuable when custom roles and product growth make manual reasoning unreliable. Positive tests show that the right users can do the right things. Negative cross-tenant tests prove that the wrong users cannot cross boundaries. In SaaS, those negative tests are often the more important ones.
An illustrative authorization test matrix looks like this:
| Scenario | Expected result |
|---|---|
| Correct tenant, sufficient role, active membership | Allow |
| Wrong tenant, same role name | Deny |
| Correct tenant, insufficient role | Deny |
| Suspended membership | Deny |
| Expired guest access | Deny |
| Missing plan entitlement | Deny |
| Failed ABAC condition | Deny |
| Resource owner or explicitly shared relationship | Allow |
| Hidden field in GraphQL/object response without property access | Deny or omit field |
| Background job missing tenant context | Deny or fail closed |
This matrix reflects OWASP’s emphasis on authorization matrices and data-level testing, OWASP API Security’s object/property authorization concerns, and AWS’s insistence that tenant isolation be explicit.
The test stack itself should include unit tests for policy logic, integration tests for realistic request paths, cross-tenant negative tests, role-hierarchy tests, custom-role tests, cache-invalidation tests, migration tests, concurrency tests, performance tests, and audit verification. OPA supports policy testing and decision logs, Cedar returns diagnostics that can help during policy validation, and externalized systems in general make policy behavior easier to exercise independently from route handlers.
Property-based tests are particularly valuable when you have many combinations of role, tenant, membership status, attribute state, and relationship structure. They can generate cases such as “any request where resource.tenant_id != active_tenant_id must deny unless an explicit cross-tenant delegation policy is present.” Concurrency tests matter too: permission changes, role revocations, or support-expiry events should not produce inconsistent decisions across caches and services for longer than your documented propagation window. Zanzibar’s emphasis on consistency under ACL changes is a reminder that correctness under change is a first-class requirement, not an optimization detail.
Penetration testing still matters, but it should be grounded in your permission model. Test ID tampering, forced browsing, list/export overreach, property overposting, GraphQL field expansion, cache collisions, signed URL abuse, impersonation workflows, and stale-session races. The goal is not only to find exploitable bugs; it is to validate that your enforcement assumptions hold in the messy reality of a live system.
Adoption Strategy, Build or Buy, FAQ, and Publication Pack
Build or buy the authorization layer
There is no universally correct answer to the build vs buy an authorization system question. The right choice depends on model complexity, latency budgets, consistency needs, auditability, team expertise, and operational tolerance. OPA is a general-purpose policy engine that decouples decision-making from enforcement. Cedar is a policy language designed for common authorization use cases including RBAC and ABAC. Amazon Verified Permissions externalizes fine-grained authorization and centralizes policy management with schema validation. OpenFGA and SpiceDB are relationship-oriented systems inspired by Zanzibar.
| Approach | Best fit | Strengths | Tradeoffs |
|---|---|---|---|
| Embedded authorization in application code | Simple tenant-scoped RBAC with low policy churn | Lowest moving-part count, easy local debugging, no extra network hop | Logic spreads across services, weaker auditability, harder custom-role evolution |
| External policy engine | RBAC + ABAC with multiple services and governance needs | Centralized policies, policy-as-code workflows, reusable enforcement patterns, better observability | Policy/data distribution complexity, operational burden, latency design needed |
| Relationship system | Products where access depends on sharing, ownership, groups, or hierarchy | Natural modeling of collaboration, inherited access, graph-style checks | Requires tuple/graph modeling discipline, consistency and recursion concerns |
This comparison is grounded in the official positioning of OPA, Cedar, Verified Permissions, OpenFGA, and SpiceDB. It is intentionally neutral: no vendor or project is “best” in the abstract because the underlying problem shapes the right tool.
An internal solution remains reasonable when your access model is mostly tenant-scoped RBAC, your product surface is modest, and one team can realistically keep authorization logic centralized behind a common library or service boundary. Dedicated authorization infrastructure becomes easier to justify when multiple product teams need consistent policy behavior, custom roles have become a sales-critical feature, ABAC rules are growing, or collaboration/sharing requirements are forcing graph-like relationship checks. Zanzibar’s paper makes a broader point here: a unified authorization system saves engineering effort when many services share security semantics and infrastructure such as search or cross-service embedding.
Migration roadmap
A safe modernization path is incremental, not revolutionary.
| Phase | What to do | Why it matters |
|---|---|---|
| Inventory authorization checks | Find every allow/deny path in APIs, jobs, exports, admin tools, search, and storage | You cannot secure what you have not enumerated |
| Define actors, actions, resources, scopes | Create the canonical vocabulary | Prevent logic drift and naming chaos |
| Establish authoritative tenant boundaries | Decide which tenant owns which resources | Isolation depends on ownership, not labels |
| Create stable permission identifiers | Use machine-friendly names like project.read | Enables reusable roles and safer migration |
| Centralize through an authorization interface | Put checks behind one callable abstraction | Reduces scattered logic |
| Move roles from users to memberships | Make access tenant-scoped | Supports multi-tenant users safely |
| Add attributes and relationships | Introduce ABAC and ReBAC where needed | Avoid role explosion |
| Run in shadow mode | Evaluate new and old models together | Detect mismatches without breaking production |
| Compare decisions and reasons | Record divergence | Builds confidence and reveals hidden edge cases |
| Add observability | Emit decision IDs, reasons, tenant context | Speeds debugging and audits |
| Enforce gradually by risk | Start with high-risk operations and cross-tenant surfaces | Limits blast radius |
| Remove legacy checks | Eliminate bypass paths | Prevent policy split-brain |
This roadmap synthesizes OWASP’s recommendations for explicit authorization logic, policy testing, and deny-by-default with AWS’s and Azure’s emphasis on explicit tenant context and ownership validation.
Backward compatibility is the painful part. Existing customers may have custom roles encoded as UI checkboxes, legacy booleans, or undocumented support overrides. Migrate them with previews, compatibility maps, and audit logs. Resist the temptation to “simplify” by granting broader access during transition. In authorization work, temporary broadening often becomes permanent.
Practical checklist
| Check | Yes/No question |
|---|---|
| Authoritative ownership | Do all protected resources have an authoritative tenant owner? |
| Membership checks | Is access evaluated against membership state, not just user identity? |
| Server-side enforcement | Are permissions enforced on the server for every request path? |
| List/export protection | Are list, search, export, and analytics paths scoped before data assembly? |
| Background-job context | Does every job carry or resolve explicit tenant context? |
| Cache isolation | Are cache keys and namespaces tenant-aware? |
| Storage isolation | Are object-storage paths and signed URLs tenant-scoped? |
| Permission/entitlement separation | Are runtime permissions separate from plan entitlements? |
| Custom-role versioning | Can roles evolve without silent privilege expansion? |
| Support access | Is internal support access temporary, approved, and audited? |
| Session revocation | Are session, token, and API-key revocation paths defined? |
| Audit trails | Do audit events include actor, tenant, action, resource, decision, and reason? |
| Negative testing | Do you run wrong-tenant and wrong-context deny tests continuously? |
| Deny by default | Do new resources/actions default to inaccessible until explicitly allowed? |
| Policy observability | Can you explain why the system allowed or denied a request? |
This checklist is a synthesized operating standard built from OWASP’s authorization and multi-tenant guidance, PostgreSQL’s default-deny RLS behavior, and the observability patterns supported by external policy engines.
How Intersog can help
For companies modernizing SaaS user management, Intersog can help with architecture assessment, multi-tenant platform engineering, permission-model redesign, enterprise SSO and SCIM rollout, cloud and DevOps hardening, and authorization-focused QA. If you need one partner across product design and implementation, Intersog’s SaaS development services and custom software development services are the most natural entry points.
FAQ
What is SaaS user management?
It is the combined system for identities, tenant memberships, invitations, roles, permissions, lifecycle events, enterprise provisioning, and access administration in a SaaS product. It is broader than login and broader than a roles screen.
What is the difference between RBAC and ABAC?
RBAC grants access through roles and permission bundles. ABAC evaluates attributes of the subject, resource, action, and environment against policy. RBAC is simpler; ABAC is more contextual.
Is RBAC enough for multi-tenant SaaS?
It is usually enough as a baseline, especially when scoped per tenant. It stops being enough when decisions depend on thresholds, ownership, sharing, geography, time, device state, or hierarchy.
What are tenant-level permissions?
They are permissions evaluated within an explicit tenant context, using tenant membership, resource ownership, and tenant boundaries rather than a global user role alone.
Should roles belong to users or memberships?
In multi-tenant SaaS, roles should usually belong to tenant memberships, because one user may have different roles in different tenants.
What is the difference between permissions and entitlements?
Permissions answer whether a membership may perform an action. Entitlements answer whether the tenant has purchased or been provisioned for a feature. Both may need to pass for access to be granted.
When should SaaS use ReBAC?
Use it when permissions depend on relationships such as owner, viewer, team member, parent folder, document share, nested group, or organization hierarchy.
How do you prevent cross-tenant access?
Resolve tenant context explicitly, verify active membership, validate resource ownership server-side, enforce tenant boundaries in every layer, and run negative cross-tenant tests continuously.
Should permissions be stored in JWTs?
Some coarse claims can be, but dynamic permission state should not rely solely on long-lived JWT contents. JWTs are compact claim containers, not a live authorization database; revocation and re-evaluation remain necessary.
How should custom roles be implemented?
Use stable permission identifiers, tenant-scoped role definitions, explicit migration/versioning, deny-by-default behavior for new permissions, and automated authorization regression tests.
How should support teams access customer accounts?
Through temporary, approved, audited support-access workflows with explicit tenant context and clear revocation, not hidden permanent super-admin access.
When should a company use an external authorization service?
When multiple services need consistent policy decisions, ABAC rules are growing, custom roles are core to the product, or relationship-based access is becoming difficult to express and audit in application code alone.
Conclusion
The decision framework is simple even if the implementation is not. RBAC is the baseline. ABAC adds context. ReBAC models relationships. Entitlements are a separate product concern. Tenant isolation must be explicit. The strongest SaaS platforms are not the ones with the flashiest permissions UI; they are the ones that centralize decisions, verify resource ownership, enforce tenant boundaries everywhere, and invest in negative testing as seriously as they invest in feature delivery. AWS, OWASP, NIST, Azure, and Zanzibar all point to the same conclusion: in multi-tenant SaaS, access control is architecture.
Leave a Comment