RBAC vs. ABAC — how do these access-control models differ, and when do you need attribute-based rules?
RBAC grants access by job role while ABAC evaluates user, resource, and context attributes at request time — most real systems eventually need both.
LEARN BY DOINGPut your intuition to work3 steps
RBAC vs. ABAC — how do these access-control models differ, and when do you need attribute-based rules?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#RBAC: access follows the role
Role-based access control assigns permissions to roles, and roles to users. "Editor" can write documents; "viewer" can only read them; "admin" can do both plus manage users. The access decision is a lookup: does this user have a role that includes the permission the action requires? It's simple to reason about, simple to audit ("show me everyone with the admin role"), and simple to implement — a join table between users and roles, and a static mapping from roles to permissions, covers most of it.
The limitation shows up as soon as a rule needs information that isn't "which role does this user have." "Editors can edit documents, but only ones they authored." "Support agents can view customer PII, but only for customers in their assigned region." "Finance can approve payments, but not payments they submitted themselves" (separation of duties). None of these are expressible as "does the user have role X" — they depend on the relationship between the user, the specific resource, and sometimes the moment of the request.
#ABAC: access follows a policy over attributes
Attribute-based access control replaces the role lookup with a policy evaluated against attributes of three things: the subject (department, clearance level, employment status), the resource (owner, classification, current state), and the context (time of day, source IP, whether MFA was used this session). A single ABAC rule can express "a user may delete a resource only if their clearance exceeds the resource's classification, the request comes from the corporate network, and it isn't a resource they created themselves" — a rule RBAC has no vocabulary for, because none of those facts live on a role.
#RBAC in code
type Role = "admin" | "editor" | "viewer";interface User {id: string;roles: Role[];}const ROLE_PERMISSIONS: Record<Role, string[]> = {admin: ["document:read", "document:write", "document:delete", "user:manage"],editor: ["document:read", "document:write"],viewer: ["document:read"],};function hasPermission(user: User, permission: string): boolean {return user.roles.some((role) => ROLE_PERMISSIONS[role]?.includes(permission));}function deleteDocument(user: User, documentId: string) {if (!hasPermission(user, "document:delete")) {throw new Error("Forbidden: missing document:delete permission");}// proceed with deletion...}
This answers "can users with this role delete documents at all" — a coarse, resource-independent question. It cannot answer "can this user delete this specific document," because documentId never enters the decision.
#ABAC in code
interface Subject {id: string;department: string;clearanceLevel: number;}interface Resource {ownerId: string;department: string;classification: number;status: "draft" | "published" | "archived";}interface AccessContext {action: "read" | "write" | "delete";time: Date;ipIsCorporateNetwork: boolean;}type PolicyRule = (subject: Subject, resource: Resource, ctx: AccessContext) => boolean;const rules: PolicyRule[] = [// Owners can always read or write their own resource.(subject, resource, ctx) =>resource.ownerId === subject.id && ctx.action !== "delete",// Same-department users can read published resources.(subject, resource, ctx) =>ctx.action === "read" &&resource.status === "published" &&subject.department === resource.department,// Deletion requires higher clearance than the resource's classification,// AND must originate from the corporate network.(subject, resource, ctx) =>ctx.action === "delete" &&subject.clearanceLevel > resource.classification &&ctx.ipIsCorporateNetwork,];function isAuthorized(subject: Subject, resource: Resource, ctx: AccessContext): boolean {return rules.some((rule) => rule(subject, resource, ctx));}
Every rule now takes the specific resource and the specific context as inputs. Adding "only during business hours" or "only if MFA was completed this session" is a new predicate, not a schema migration — no new role needs to be invented and assigned to anyone.
#A concrete comparison
| RBAC | ABAC | |
|---|---|---|
| Decision depends on | The user's assigned role(s) | Attributes of subject + resource + context |
| Granularity | Coarse — same for every resource of a type | Fine — can differ per resource instance |
| Auditability | Easy — enumerate role assignments | Harder — must trace which rule fired and why |
| New rule cost | Often needs a new role + reassigning users | Often just a new predicate |
| Performance | A cache-friendly lookup | Can require fetching resource/context attributes per check |
| Explosion risk | Role explosion (a role per resource/condition combo) | Rule complexity explosion, hard-to-audit policy sprawl |
#When RBAC alone genuinely isn't enough
The tell is when you catch yourself inventing roles like editor-for-team-alpha, approver-under-10k, or support-region-us-east — that's RBAC being stretched to encode attributes (team, amount, region) into role names, because the model has no other place to put them. Once you're multiplying roles by every dimension a policy actually depends on, you've reinvented ABAC badly, without the composability.
#The practical answer: layer them
Almost no production system is pure ABAC — evaluating a full attribute policy on every request is more expensive and harder to reason about than necessary for coarse gates like "is this user even allowed to access the admin panel at all." The common pattern is RBAC for the coarse, cheap, cacheable gate (does this user have any business being here), and ABAC for the fine-grained decision once inside (which specific records can they touch, under what conditions). Tools like OPA (Open Policy Agent) and AWS IAM's condition keys are ABAC engines bolted onto systems that still use roles as their first filter.
🧠 Check your understanding
0/3 · 0/3 answered1. Which requirement is RBAC fundamentally unable to express without inventing a new role per case?
2. In the ABAC code example, what determines whether a delete is authorized?
3. What is a common warning sign that a team is straining RBAC past its useful limits?