Most SaaS founders discover their permission model is broken the same way: an enterprise prospect's security team asks to see it. What gets shared — a flat admin/member toggle, maybe a "read-only" flag — causes the deal to stall or die. Access control is one of the most systematically underinvested areas in early B2B SaaS, and it becomes one of the most expensive to retrofit once you are trying to close $50K+ contracts.
Role-Based Access Control, or RBAC, is the dominant access model in enterprise software for good reason: it maps closely to how organisations actually work. Users have job functions; job functions have permissions; permissions gate resources. But the practical implementation for a multi-tenant B2B SaaS involves considerably more nuance than assigning a role field to a user record. This article covers the design decisions that matter.
Why Flat Permissions Break at Enterprise Scale
A typical early SaaS product starts with something like: is_admin: boolean. Then a customer asks for a read-only analyst role. You add a flag. Then they ask for a role that can manage billing but not users. Another flag. Then a different customer asks for per-project permissions. At this point you have accumulated a cluster of overlapping boolean fields with complex interactions that no single engineer fully understands — and certainly no audit report can accurately summarise.
The enterprise buyer's concern is not theoretical. Their legal and compliance teams need to demonstrate that access to sensitive data is governed, auditable, and revocable. A permissions model that cannot produce a clean answer to "who has access to customer records and why" is a procurement blocker.
The Core RBAC Model: Entities, Roles, and Permissions
A robust RBAC implementation has four primary entities:
- Subject — the user or service account taking an action
- Role — a named collection of permissions assigned to subjects (e.g., Account Admin, Billing Manager, Read-Only Viewer)
- Permission — a specific action on a specific resource type (e.g., invoices:read, users:delete, reports:export)
- Resource — the object being acted upon, which may itself have ownership or scope (e.g., a specific project, a workspace)
The relationship between them: a subject has one or more roles; each role grants one or more permissions; each permission is evaluated against the resource being accessed.
In database terms, you typically need a roles table, a permissions table, a role_permissions join table, and a user_roles join table. The permission check at runtime becomes: does this user have a role that grants this permission?
Handling Organisational Hierarchies
B2B SaaS complicates RBAC because your customers are organisations, not individuals. A typical hierarchy looks like:
- Platform level — your own super-admins who can access any tenant (for support purposes)
- Organisation level — the customer company; an org admin manages users within this boundary
- Workspace or project level — sub-units within the org; a user may have different roles in different workspaces
- Resource level — individual records (a document, a campaign, a dataset) that may have fine-grained ownership
This is where flat RBAC starts to strain. A user might be a viewer in Workspace A and an editor in Workspace B. The naive solution is to store a role per workspace per user, which works but creates complexity in the permission check: you now have to evaluate not just the user's global role but their role within the current workspace context.
The cleaner approach separates global roles (platform-wide, usually for internal teams) from contextual roles (scoped to an org or workspace). Most enterprise B2B SaaS products end up with both — an organisational admin who can manage workspace membership, and workspace-level roles that govern what actions a member can take within that workspace.
Permission Granularity: How Fine Is Fine Enough?
There is a real trade-off between permission granularity and usability. Too coarse and your roles do not satisfy enterprise requirements; too granular and your customers' admins need a manual to configure them.
A workable pattern is to define permissions at the action-resource level using a consistent naming scheme:
| Resource | Permission | Description |
|---|---|---|
| users | users:invite | Can invite new members to the organisation |
| users | users:remove | Can remove members from the organisation |
| billing | billing:read | Can view invoices and subscription details |
| billing | billing:manage | Can change subscription plan and payment method |
| reports | reports:view | Can view reports within their scope |
| reports | reports:export | Can export report data to CSV or API |
| api_keys | api_keys:create | Can create API credentials |
Roles then bundle these permissions in ways that reflect real job functions. Your default roles (Owner, Admin, Member, Viewer) cover the majority of use cases. For enterprise customers who need custom roles, expose a role-builder UI where admins can compose roles from the permission set you define — but you define the atomic permissions, not them.
Custom Roles and Enterprise Flexibility
Mid-market and enterprise customers frequently request custom roles specific to their internal team structure. A security operations team might need a role that has audit log access but cannot modify any data. A finance team might need billing access with no product access whatsoever.
Supporting custom roles requires a small schema addition: a custom flag on the roles table, and logic that treats system roles (Owner, Admin, Member) as immutable while allowing organisation admins to create, modify, and delete their own custom roles. The constraint you should enforce: custom roles cannot exceed the permissions of the creator. An Admin cannot create a custom role that grants Owner-level permissions they do not themselves hold.
This constraint — known as permission inheritance or permission delegation — prevents privilege escalation through role manipulation and is something enterprise security reviewers specifically check for.
Audit Trails: The Enterprise Non-Negotiable
RBAC without an audit log satisfies access control but fails compliance. Enterprise buyers in regulated industries — financial services, healthcare, legal, insurance — require a durable, tamper-evident record of who accessed what and when.
What a minimum viable audit log needs to capture:
- Actor (user ID, IP address, user agent)
- Action taken (the permission exercised)
- Resource affected (resource type and ID)
- Timestamp (in UTC, immutable)
- Outcome (success or denial)
- Context (organisation ID, workspace ID, session ID)
The key implementation detail: audit logs must be written to a separate, append-only store. An audit log that can be modified by a super-admin in the same database as the application data does not satisfy a SOC 2 or ISO 27001 auditor. Many teams solve this by streaming audit events to an immutable object store (like S3 with Object Lock) or a write-once logging service, separate from the primary RDBMS.
Teams at Mexilet Technologies regularly scope RBAC and audit infrastructure as part of enterprise-readiness sprints — it is a well-understood pattern, but the details matter significantly for compliance certification.
Enforcing Permissions Consistently Across API and UI
The most common RBAC implementation failure is enforcing permissions only in the UI. The backend API endpoints must enforce them independently. An API-first permission check means: every request to a protected endpoint resolves the current user's permission set, evaluates the required permission, and returns 403 if it is not held — regardless of what the frontend might or might not show.
Practically, this means building a permission-check middleware or decorator that runs before your route handlers. Something like:
- Extract the authenticated user from the session or JWT
- Load their effective permissions for the current organisational context
- Check the required permission for this endpoint
- Deny or allow accordingly, and write to the audit log
Caching the user's effective permission set per session (in Redis, for example) avoids hitting the database on every request while keeping latency acceptable. Invalidate the cache on role assignment changes.
Frequently Asked Questions
What is the difference between RBAC and ABAC, and which should a B2B SaaS use?
Attribute-Based Access Control (ABAC) evaluates permissions against arbitrary attributes of the subject, resource, and environment — for example, "allow access if the user's department matches the document's department and it is between 9am and 5pm." ABAC is more expressive but significantly harder to audit and reason about. For most B2B SaaS products, RBAC with contextual scoping covers 90% of enterprise requirements with far less implementation complexity. ABAC is worth considering only when you have genuinely complex, attribute-driven access rules that RBAC cannot express cleanly.
How should we handle API key permissions in an RBAC system?
API keys should be treated as service accounts and assigned roles just like human users. The critical constraint: an API key should never be able to be issued with more permissions than the user who created it. Many teams also add a resource-scoping layer to API keys — for example, a key that can only read data from a specific workspace — which is a stricter model than the issuing user's full role set. Store keys as hashed values, not plaintext, and emit an audit event on every use.
When should we build RBAC in-house versus using an off-the-shelf solution?
For most SaaS products, building core RBAC in-house is reasonable and gives you the flexibility to model your specific resource hierarchy. Off-the-shelf authorisation services (like OPA, Oso, or managed IAM providers) are worth evaluating when your permission model becomes genuinely complex — for example, when you need relationship-based access control (ReBAC) where permission depends on a graph of ownership relationships rather than a simple role assignment. Avoid adding a third-party dependency for basic role management; it adds cost and operational complexity without proportional benefit at early scale.
Does RBAC need to be documented for SOC 2 compliance?
Yes. SOC 2 Type II audits examine access control as a core Trust Services Criterion. You need to document your RBAC model, demonstrate that it is enforced (via audit logs), show that access is reviewed periodically, and show that access is revoked promptly when employees leave. The RBAC system itself is the mechanism; the documentation and the audit log are the evidence. Without both, passing the audit is difficult regardless of how well the code works.
Mexilet Technologies supports teams on exactly this kind of work through our SaaS development services and product engineering team.
If your team is about to spec out or overhaul an access control system and you want to avoid the common design pitfalls, book a free technical scoping call with Mexilet Technologies. We can map your specific permission model, tenant hierarchy, and compliance requirements into a concrete implementation plan before you write the first line of code.