Authorization Models Compared: RBAC, ABAC, and ReBAC
Compare RBAC, ABAC, and ReBAC authorization models to pick the right access control strategy. Learn which permission shapes each handles and when to upgrade.

Most access control bugs aren't bugs in the code. They're mismatches between the authorization model and the permission shapes the product actually needs. A startup ships RBAC because it's the obvious starting point, then spends the next year bolting on attribute checks and relationship lookups that the model was never designed to support. The result: role proliferation, hardcoded special cases, and logic scattered across service layers.
Choosing the right authorization model up front (or recognizing when you've outgrown the current one) is a design decision, not an implementation detail. The three dominant models each map cleanly to a certain class of permissions. RBAC handles role-scoped flat hierarchies. ABAC handles policies driven by object and subject attributes. ReBAC handles permissions that live in a relationship graph. Knowing where each model breaks is the key to picking the right one.
This post walks through all three, maps them to real product permission patterns, and names the concrete signals that tell you it's time to graduate from one to the next.
What you'll learn
- What RBAC is and where it works
- Where RBAC breaks down under scale
- What ABAC adds and when attributes matter
- ReBAC and graph-native permission models
- Side-by-side comparison of the three models
- Signals that you've outgrown your current model
- Implementing authorization in practice
- Frequently Asked Questions
What RBAC is and where it works
Role-Based Access Control (RBAC) is an authorization model that grants permissions to roles and then assigns roles to users. A user's effective permissions are the union of everything their roles allow. The model is dead simple, easy to audit, and maps naturally onto org charts: an admin role can delete records, an editor role can write them, a viewer role can only read.
RBAC works cleanly for any product where:
- Permissions are uniform across all resource instances (every document behaves the same)
- The number of distinct permission patterns is small enough to enumerate as roles
- Users carry their permissions with them regardless of context
Internal tools, admin dashboards, and B2B SaaS products with a handful of plan tiers are natural fits. If your product has three roles and a clear mapping from role to capability, RBAC is genuinely the right answer. Don't over-engineer it.
The implementation is straightforward. You check currentUser.roles.includes('admin') or query a join table. Most frameworks ship RBAC primitives out of the box. The cost is low and the auditability is high.
Where RBAC breaks down under scale
The failure mode of RBAC is role explosion. Products that start with three roles creep toward thirty, then three hundred, as teams try to express per-tenant, per-department, or per-resource variations by stacking roles. editor, tenant-a-editor, tenant-a-editor-restricted, and tenant-b-editor-archived are all covering ground that RBAC wasn't designed to handle.
Two concrete signals that you've hit the ceiling:
Signal 1: You're encoding resource identity inside role names. If role names contain IDs, tenant slugs, or document paths, you're using roles as a workaround for attribute-based logic you haven't built yet.
Signal 2: The same user needs different permissions on different instances of the same resource type. A user who can edit project-123 but only view project-456 can't be cleanly represented with flat roles unless you create one role per project. That doesn't scale.
Here's what the role explosion looks like in a permissions table:
-- Role explosion anti-pattern
INSERT INTO user_roles VALUES
(user_id, 'tenant-a-project-123-editor'),
(user_id, 'tenant-a-project-456-viewer'),
(user_id, 'tenant-b-project-789-admin');
Each new project or tenant forces a new role. This is the point where RBAC is doing the wrong job.
What ABAC adds and when attributes matter
Attribute-Based Access Control (ABAC) is an authorization model that evaluates policies against the attributes of the subject (user), the resource, and the environment. Instead of assigning a user a role that carries permissions, you write a policy that says "a user can edit a document if user.department == document.department and user.clearanceLevel >= document.sensitivityLevel."
ABAC shines when:
- Permissions depend on properties of the resource, not just its type
- Environmental conditions (time of day, IP range, MFA status) affect what's allowed
- The number of permission combinations would make role enumeration impractical
A good example is a healthcare records system where a clinician can only read records for patients assigned to their ward, and only during their shift hours. That policy has three moving parts: the clinician attribute, the patient-assignment attribute, and a time condition. RBAC can't express it without encoding the ward into the role and the shift into something else entirely.
The trade-off is real. ABAC policies are harder to audit. You can't ask "what can this user do?" without evaluating every policy against every resource. Policy languages like XACML or OPA's Rego can get complex fast. Teams at Laxaar that have migrated from RBAC to ABAC consistently report the same thing: the policy library needs governance tooling from day one. Without it, policy drift becomes a security problem of its own.
ReBAC and graph-native permission models
Relationship-Based Access Control (ReBAC) is an authorization model where permissions are derived from the relationship graph between subjects and objects. The question isn't "does this user have the editor role?" or "does this document have department == finance?" It's "does a path exist between this user and this document in the relationship graph that grants edit access?"
Google's Zanzibar paper, which underpins systems like Auth0 FGA, SpiceDB, and Permify, is the canonical ReBAC reference. A Zanzibar-style tuple looks like:
document:report-q1#viewer@user:alice
document:report-q1#viewer@group:finance#member
folder:q1-reports#parent@document:report-q1
These three tuples say: Alice is a viewer of report-q1, the finance group's members are viewers, and report-q1 inherits from the q1-reports folder. If you add a new document to the folder, everyone with folder access automatically gains the inherited permission. No role update required.
ReBAC handles the permission shapes that defeat both RBAC and ABAC:
- Resource ownership ("only the creator can delete this")
- Nested sharing ("a user who has access to a workspace has access to all projects in it")
- Fine-grained per-resource, per-user grants ("share this specific file with bob as editor")
- Org-chart delegation ("a manager can approve any request from their direct reports")
The cost is infrastructure. You're running a separate authorization service that stores relationship tuples, evaluates path queries, and has to stay consistent with your primary database. At Laxaar, we've seen teams underestimate this operational burden. If you don't need relationship-derived permissions, you're adding latency and a new failure domain for no gain.
Side-by-side comparison of the three models
| Dimension | RBAC | ABAC | ReBAC |
|---|---|---|---|
| Permission source | Role membership | Policy evaluation against attributes | Relationship graph traversal |
| Best for | Uniform resource types, org-scoped permissions | Policy-driven, attribute-conditional access | Per-resource sharing, nested inheritance |
| Audit simplicity | High — list a user's roles | Medium — evaluate policies per resource | Low — requires graph traversal to explain |
| Scaling signal | Role explosion with resource variants | Fine when policies stay readable | Required when sharing models are user-driven |
| Example tooling | Any ORM with join tables, Casbin | OPA, Cedar, XACML | SpiceDB, Auth0 FGA, Permify, Oso |
| Operational cost | Low | Medium | High |
No model is universally better. The question is whether your permission shapes fit the model you've chosen.
Signals that you've outgrown your current model
Moving between models is expensive, so it's worth naming the real signals rather than chasing theoretical elegance.
From RBAC to ABAC: You're creating roles that encode resource attributes (department, classification, tier). You have more than roughly 20-30 active roles and the list keeps growing. Policy reviewers can no longer explain in plain English why a given user has access to a given resource.
From ABAC to ReBAC: Your access decisions depend on transitive relationships, not just the immediate attributes of the subject and object. A user's permission on a resource changes based on who shared it with them, or inherits through a folder or workspace hierarchy. Attribute policies are becoming increasingly complex because they're trying to simulate a graph walk.
Staying with RBAC: Your product has a stable, small role set. Resource instances are interchangeable from a permission standpoint. Your engineering team is small and authorization is not a product differentiator. Don't add complexity you don't need.
The honest trade-off is that organizations often try to stretch RBAC one size too large because ABAC and ReBAC both require dedicated investment. That's a reasonable short-term call. Just document the decision and the expected graduation signal so future maintainers understand the constraint.
Implementing authorization in practice
Keep authorization logic out of your application business code. It belongs in a dedicated layer (a service, a library, or an external system) that your application calls. This keeps the auth model swappable and the rest of your code readable.
A minimal RBAC check in a Next.js API route:
// lib/authz.ts
export function canEditDocument(user: User, document: Document): boolean {
if (user.roles.includes('admin')) return true;
if (user.roles.includes('editor') && document.ownerId === user.id) return true;
return false;
}
// app/api/documents/[id]/route.ts
export async function PUT(req: Request, { params }: { params: { id: string } }) {
const user = await getCurrentUser(req);
const doc = await getDocument(params.id);
if (!canEditDocument(user, doc)) {
return new Response('Forbidden', { status: 403 });
}
// proceed with update
}
For OPA-based ABAC, your policy lives in a .rego file and your application sends an authorization request to the OPA sidecar. For SpiceDB-style ReBAC, you write relationship tuples on create/share events and query check on each access attempt.
The Laxaar team recommends starting with whatever is simplest for your current permission shapes, naming the file or module clearly (authz.ts, not utils.ts), and planning for the migration rather than assuming you'll never need it. Building your custom software with a clean authz boundary from day one is far cheaper than retrofitting it.
You can see examples of how we've applied these patterns in different product contexts across our portfolio. For teams starting fresh, our web development services include architectural review of the auth layer as a default deliverable.
If you're working through an authorization model decision for an existing product, the Laxaar team is happy to review your current setup and flag where the friction will surface first.
Frequently Asked Questions
Can you combine RBAC and ABAC in the same system?
Yes, and most production systems do. A common pattern is using RBAC for coarse-grained access (is this user an admin or a viewer?) and adding attribute checks for fine-grained policy within a role (a viewer can only see records where record.region == user.region). The risk is that the two layers grow independently and become inconsistent, so keep the policy logic in one place rather than splitting it across roles and code.
When is ReBAC actually worth the added complexity?
ReBAC earns its cost when your product's sharing model is end-user-driven. If users can share individual documents, folders, or workspaces with other users or groups, and those shares need to cascade through hierarchies, you've described a relationship graph. Any other model will require you to approximate that graph with increasing awkwardness. Collaboration products, project management tools, and document editors are the canonical cases.
Does my authorization model affect performance?
It does, especially at read scale. RBAC lookups are cheap: a role membership query against an indexed join table is fast. ABAC adds policy evaluation overhead, which is usually acceptable but can grow if policies are evaluated against many resources in a list query. ReBAC graph traversal is the most expensive; systems like Zanzibar solve this with aggressive caching of relationship tuples. If you're building a high-read product, factor the p99 latency of your auth check into your architecture early.
What's the difference between authentication and authorization?
Authentication confirms who a user is, by validating a password, a token, or a biometric. Authorization determines what that confirmed user is allowed to do. They're separate concerns and should live in separate layers of your stack. Most auth libraries (Clerk, Auth0, NextAuth) handle authentication and provide a user identity object; what you do with that identity (RBAC, ABAC, ReBAC) is your authorization logic and your responsibility to implement correctly.
Is OPA (Open Policy Agent) RBAC or ABAC?
OPA is a general-purpose policy engine that can implement either model, or a mix of both. It evaluates Rego policies against any input you send it, which makes it flexible enough to express role checks, attribute conditions, and even simple relationship lookups. It's not a ReBAC system by default. It doesn't store or traverse relationship tuples natively, but you can integrate it with a relationship store if needed.
Authorization model selection is one of those decisions that feels low-stakes at the start and becomes high-stakes surprisingly fast. Pick the model that fits your actual permission shapes today, and invest in a clean separation between your authz layer and your business logic so the next model is a refactor, not a rewrite.
The Laxaar team has helped product teams audit and redesign their authorization layers at every scale, from early-stage SaaS to enterprise platforms with complex multi-tenant permission requirements. If you're seeing role explosion, access bugs, or escalating audit complexity, get in touch and we'll walk through what the right model looks like for your specific product.
Working on something like this?
Get a fixed scope, timeline, and price within one business day — no obligation.


