Authorization is one of the most security-critical parts of any SaaS application, and it is consistently one of the weakest. In our secure code review engagements, broken access control appears in the top three findings more often than any other vulnerability category. The root cause is almost always the same: the application either has no coherent access control model, or it has one that was implemented inconsistently.
RBAC (Role-Based Access Control) and ABAC (Attribute-Based Access Control) are the two dominant models for solving this problem. Each has clear strengths and weaknesses, and choosing the wrong one, or implementing the right one poorly, creates the exact authorization vulnerabilities that attackers and pentesters exploit.
This guide covers how each model works, when to use which, the implementation mistakes we see most often, and how to build an access control layer that actually holds up under testing.
How RBAC Works
Role-Based Access Control assigns permissions to roles, and roles to users. Instead of granting individual permissions to each user, you define a set of roles (admin, editor, viewer, billing admin, etc.) and assign a bundle of permissions to each role. Users inherit all the permissions of their assigned role.
The model is straightforward:
- User is assigned one or more roles
- Each role has a defined set of permissions
- When a user tries to perform an action, the system checks whether any of their roles include the required permission
For example, in a project management SaaS, you might have a "Project Manager" role that can create projects, assign tasks, and view all project data, and a "Team Member" role that can view assigned tasks and update their status but cannot create projects or view other teams' work.
Advantages of RBAC
- Simple to understand. Everyone intuitively understands roles. Explaining "you are a viewer, viewers can see but not edit" requires no technical background
- Easy to audit. You can look at a user's role and immediately understand what they can do. This makes access reviews straightforward and compliance evidence clean
- Low implementation overhead. A basic RBAC system requires a roles table, a user-role mapping, and middleware that checks the user's role against the required role for each endpoint
- Widely supported. Almost every framework and identity provider has built-in RBAC support
Limitations of RBAC
- Role explosion. As access requirements become more nuanced, you need more roles to represent them. "Editor who can only edit their own department's content during business hours" does not map cleanly to a single role
- No context awareness. RBAC makes decisions based solely on the user's role. It cannot factor in the resource being accessed, the time of day, the user's location, or other contextual attributes
- Coarse-grained by default. RBAC is best at answering "can this user access this feature?" and weaker at answering "can this user access this specific record?"
How ABAC Works
Attribute-Based Access Control makes access decisions based on attributes (properties) of the user, the resource, the action, and the environment. Instead of looking up a user's role, an ABAC system evaluates a policy that considers multiple attributes simultaneously.
The model evaluates policies against four categories of attributes:
- Subject attributes: Properties of the user (department, job title, clearance level, location)
- Resource attributes: Properties of the data or system being accessed (classification, owner, creation date, type)
- Action attributes: What the user is trying to do (read, write, delete, approve)
- Environment attributes: Contextual factors (time of day, IP address, device type, risk score)
For example, an ABAC policy might state: "Users in the finance department can read financial reports that are classified as 'internal' during business hours from a corporate-managed device." This single policy replaces what might require multiple roles and custom code in an RBAC system.
Advantages of ABAC
- Fine-grained control. ABAC can express virtually any access rule, no matter how specific or contextual
- No role explosion. Instead of creating new roles for edge cases, you add attributes and policies. The model scales to complex requirements without becoming unwieldy
- Dynamic and context-aware. Access decisions can change based on real-time conditions like time, location, and device security posture
- Policy-driven. Access rules are expressed as declarative policies rather than hardcoded logic, making them easier to review, audit, and modify
Limitations of ABAC
- Higher implementation complexity. Building and maintaining a policy engine, attribute stores, and policy enforcement points requires significant engineering investment
- Harder to audit visually. Answering "what can user X do?" requires evaluating all policies against that user's attributes, which is not as simple as looking up their role
- Performance considerations. Evaluating complex policies with multiple attribute lookups on every request can introduce latency if not designed carefully
- Policy management overhead. Policies can become complex and interact in unexpected ways. Without proper governance, you can end up with conflicting policies or unintended access grants
Side-by-Side Comparison
| RBAC | ABAC | |
|---|---|---|
| Access Based On | User's assigned role | User, resource, action, and environment attributes |
| Granularity | Feature-level (can/cannot access) | Record-level with context (can access this specific resource under these conditions) |
| Implementation | Simple; roles table + middleware | Complex; policy engine + attribute stores |
| Scalability | Role explosion with complex requirements | Scales via policies and attributes |
| Auditability | Easy: look up user's role | Harder: must evaluate policies against attributes |
| Context Awareness | None by default | Built-in (time, location, device, etc.) |
| Best For | Applications with clear role hierarchies | Applications with complex, contextual access rules |
| Compliance | Simple evidence: role assignments and permissions | Policy documentation + attribute audit trails |
| Common Tools | Built-in framework support, Casbin, Keycloak | OPA/Rego, Cedar (AWS), Cerbos, Oso |
When to Use Each Model
Use RBAC when:
- Your application has a clear set of personas with distinct permission needs (admin, user, viewer)
- You are an early-stage SaaS company that needs to ship authorization quickly and iterate
- Your access requirements are feature-level rather than record-level (e.g., "can access the billing page" vs. "can access invoices for their department")
- You have fewer than 10 distinct roles needed to cover all access patterns
- Your compliance requirements emphasize clear, simple access documentation
Use ABAC when:
- You need record-level access control (users can only see their own data, their team's data, or data matching specific criteria)
- Access requirements are contextual (vary by time, location, device, or data classification)
- You are in a regulated industry where access decisions need to consider data classification levels
- Your RBAC implementation has reached role explosion (more than 20-30 roles and still growing)
- You need multi-tenant isolation with complex sharing rules between tenants
Use both (hybrid approach) when:
- You want RBAC for coarse-grained, feature-level access (what pages and actions a user can access) combined with ABAC for fine-grained, record-level access within those features (which specific records they can see and modify)
- You are migrating from RBAC to ABAC incrementally and need both to coexist during the transition
- Your application has both simple access patterns (admin/user tiers) and complex ones (multi-tenant data sharing rules)
Our recommendation for most SaaS companies: Start with RBAC. It is faster to implement, easier to test, and sufficient for most early and growth-stage applications. Add ABAC capabilities incrementally as your access requirements become more complex. The hybrid approach (RBAC for features, ABAC for data) is where most mature SaaS applications end up.
Common Implementation Mistakes We Find in Code Reviews
Regardless of which model you choose, the implementation is where things go wrong. These are the authorization vulnerabilities we find most frequently during secure code reviews and penetration testing:
Frontend-only authorization checks
This is the most critical and most common mistake. The application hides buttons, menu items, or pages based on the user's role in the frontend (JavaScript, React components, etc.) but does not enforce the same checks on the backend API endpoints. An attacker can bypass the frontend entirely and call the API directly with elevated permissions.
Every authorization check must be enforced at the API/backend level. Frontend checks are a user experience convenience, not a security control.
Missing object-level authorization
The application checks whether a user has the "editor" role but does not check whether they are authorized to edit the specific resource they are requesting. This leads to IDOR (Insecure Direct Object Reference) vulnerabilities where an editor can modify any record in the system, not just their own.
Correct implementation checks both the role (does the user have edit permission?) and the object ownership or scope (does the user have permission to edit this specific record?).
Inconsistent enforcement
Authorization is checked on the main CRUD endpoints but missed on ancillary endpoints: export functions, search endpoints, file download endpoints, webhook configurations, and API key management. Attackers specifically target these secondary endpoints because developers often forget to add authorization checks to them.
Hardcoded role checks scattered across the codebase
When authorization logic is duplicated across hundreds of controller methods (e.g., if (user.role === 'admin') sprinkled everywhere), it becomes nearly impossible to audit, easy to miss in new endpoints, and brittle to role changes. A centralized authorization layer or middleware that all requests pass through prevents this problem.
Privilege escalation through parameter manipulation
The application accepts a role parameter in API requests (e.g., creating a user account with a role field in the request body) and does not validate that the requesting user has permission to assign that role. This allows a regular user to create an admin account by including "role": "admin" in their request.
Tenant isolation failures in multi-tenant applications
In multi-tenant SaaS, the access control model must enforce that users from one tenant cannot access data belonging to another tenant. We frequently find applications where tenant isolation is enforced in most queries but missed in reporting endpoints, search functions, or data export features, allowing cross-tenant data access.
Building Authorization That Survives Testing
Based on the patterns we see across hundreds of security assessments, here are the practices that produce the most resilient authorization implementations:
- Centralize authorization logic. Use a single authorization layer (middleware, policy engine, or authorization library) that all requests pass through. Do not duplicate authorization checks in individual controllers or services
- Default to deny. Every endpoint should require explicit authorization. If a new endpoint is added without an authorization rule, it should be blocked by default rather than open by default
- Enforce at the API layer. Every API endpoint must validate authorization server-side. Frontend visibility controls are supplementary, not primary
- Check object-level access. For any operation on a specific resource, verify that the user is authorized to act on that specific resource, not just that they have the general permission type
- Test authorization explicitly. Include authorization tests in your test suite. For every endpoint, test that authorized users can access it and that unauthorized users are denied. This catches regressions immediately
- Audit regularly. Conduct periodic reviews of your authorization rules and enforcement points. Our penetration testing specifically evaluates authorization controls across your application's full API surface
Authorization Libraries and Tools
Rather than building authorization from scratch, leverage established tools that implement these models correctly:
For RBAC
- Casbin: Open-source authorization library supporting multiple models (RBAC, ABAC, and hybrid) across many languages
- Keycloak: Open-source identity and access management with built-in RBAC and fine-grained permission support
- Framework-native: Most web frameworks (Rails, Django, Laravel, Spring) have built-in or standard RBAC libraries
For ABAC
- OPA (Open Policy Agent): CNCF project for policy-based access control. Policies written in Rego. Works with any application via API
- Cedar (AWS): Policy language and evaluation engine from AWS, designed for fine-grained authorization at scale
- Cerbos: Open-source authorization layer with context-aware policy support, designed for microservices
- Oso: Authorization framework with a declarative policy language (Polar) designed specifically for application authorization
Using an established tool rather than building your own reduces the risk of implementation errors and provides a framework for policy management, testing, and auditing that you would otherwise need to build yourself.
The bottom line: The access control model you choose matters less than how well you implement it. A well-implemented RBAC system will always be more secure than a poorly implemented ABAC system. Choose the model that matches your current needs, implement it consistently across your entire application, enforce it server-side, and test it regularly. When your needs outgrow your model, extend it deliberately rather than patching around it.
Is your authorization implementation holding up?
Our penetration testing and secure code review services specifically target authorization vulnerabilities. We find the access control gaps that automated scanners miss and show you exactly how to fix them.