Role-based access control is an authorization model where permissions attach to roles, and users get permissions only through their roles. The indirection is the entire idea: nothing is ever granted to a person directly, so organizational change becomes data change — a promotion is a role reassignment, not an archaeology dig through scattered grants. Proposed by Ferraiolo and Kuhn in 1992 as an alternative to the older discretionary and mandatory models, it became the ANSI/INCITS 359 standard and the default authorization vocabulary of enterprise software.
Key takeaways
| Question | Answer |
|---|---|
| The indirection | User → role → permission — never user → permission directly |
| Role ≠ group | A group collects users; a role collects permissions |
| The model levels | Core · hierarchical (inheritance) · constrained (separation of duties) |
| The failure mode | Role explosion — attributes encoded as roles until roles outnumber users |
| The composition | Roles inside ACL entries: RBAC for the many, ACLs for the exceptions |
Users, roles, permissions — the indirection at work
Permissions Roles Users
───────────── ───────────── ─────────────
posts:read ─┐
posts:write ├──▶ Editor ◀────── Ada, Grace
posts:publish ─┘
users:manage ─┐
billing:view ├──▶ Admin ◀────── Linus
posts:* ─┘
posts:read ────▶ Viewer ◀────── everyone else
Ada publishes because Editor carries posts:publish — reassign her role,
and every permission it carried moves with it. One edit, not N.
Roles as live data, wired into object permissions:
// JavaScript / Node.js — Back4app JS SDK
// Roles as data: create a role, add members, grant through it
const roleACL = new Parse.ACL();
roleACL.setPublicReadAccess(true);
const editors = new Parse.Role('Editors', roleACL);
editors.getUsers().add(adaUser);
await editors.save();
// Grant by role, not by user — membership changes in one place
const post = new Parse.Object('Post');
const acl = new Parse.ACL(currentUser); // owner entry
acl.setRoleWriteAccess('Editors', true); // RBAC meets the object's ACL
post.setACL(acl);
await post.save(); // Flutter / Dart — Back4app Flutter SDK
// Roles as data: create a role, add members, grant through it
final roleACL = ParseACL()..setPublicReadAccess(allowed: true);
final editors = ParseObject('_Role')
..set('name', 'Editors')
..setACL(roleACL)
..addRelation('users', [adaUser]);
await editors.save();
// Grant by role, not by user — membership changes in one place
final post = ParseObject('Post');
final acl = ParseACL(owner: currentUser) // owner entry
..setRoleWriteAccess('Editors', true); // RBAC meets the object's ACL
post.setACL(acl);
await post.save(); // iOS / Swift — Back4app Swift SDK
// Roles as data: create a role, add members, grant through it
var editors = try ParseRole(name: "Editors")
let savedRole = try await editors.save()
try await savedRole.users.add([adaUser]).save() // membership = a relation
// Grant by role, not by user — membership changes in one place
var post = Post()
var acl = ParseACL()
acl.setReadAccess(user: currentUser, value: true)
acl.setWriteAccess(user: currentUser, value: true) // owner entry
acl.setWriteAccess(roleName: "Editors", value: true) // RBAC meets the ACL
post.ACL = acl
try await post.save() // Android / Kotlin — Back4app Android SDK
// Roles as data: create a role, add members, grant through it
val roleACL = ParseACL()
roleACL.publicReadAccess = true
val editors = ParseRole("Editors", roleACL)
editors.users.add(adaUser)
editors.save()
// Grant by role, not by user — membership changes in one place
val post = ParseObject("Post")
val acl = ParseACL(ParseUser.getCurrentUser()) // owner entry
acl.setRoleWriteAccess("Editors", true) // RBAC meets the object's ACL
post.acl = acl
post.save() The NIST model, correctly
Most explainers compress the standard into a listicle; the actual structure is worth thirty seconds. Core RBAC defines users, roles, permissions — and sessions, the forgotten element: a user activates a subset of their roles per session, which is how an admin can browse with member powers by default and escalate deliberately. The original paper’s three rules bind it together: act only through a role, hold only authorized roles, do only what the active role permits. Hierarchical RBAC adds inheritance — senior roles subsume junior ones (Manager ⊇ Employee), removing duplication. Constrained RBAC adds separation of duties: static rules forbid conflicting role assignments outright (never both payment-creator and payment-approver); dynamic rules allow the assignment but forbid activating both in one session. And NIST’s sharpest clarification, routinely garbled: a group is a collection of users; a role is a collection of permissions — the role is defined by what it can do, not who is in it.
RBAC vs. ACL vs. ABAC
| RBAC | ACL | ABAC | |
|---|---|---|---|
| Permissions attach to | Roles | Each object | Attribute rules |
| Native question | What can this function do? | Who can touch this object? | Is this allowed in context? |
| Administration | One place per role | Per object | Per policy |
| Per-object sharing | Can’t express it | Its home game | Expressible, verbose |
| Audit “what can Ada do?” | Read her roles | Scan every object | Evaluate every rule |
| Failure mode | Role explosion | List sprawl | Opaque policies |
The insight the vendor SERP buries: these compose, they don’t compete. Roles handle access that follows function; ACL entries handle per-object exceptions — and the hinge is the role-entry ACE, an ACL line naming a role instead of a user, giving object-level control with one-place membership. Attribute conditions layer on top where context (time, tenant, record state) genuinely decides. “Which one?” is usually the wrong question; “which layer handles which decision?” is the design.
Role explosion — the failure mode
RBAC’s characteristic disease: every exception mints a role, then every project, region, and tenant multiplies them — Project-A-Manager-Region-West-ReadOnly — until roles outnumber users and the audit answer to “who can do what?” is “nobody knows.” The root cause is always the same: contextual attributes encoded as roles. The mitigations, in order: keep attributes out of role names (region and tenant are conditions or scopes, not roles); handle per-object sharing with ACL entries, never per-object roles; scope roles per tenant structurally instead of by name-mangling; and audit — roles nobody holds, permissions no role uses, and grants nobody remembers are drift, and drift is how least privilege quietly dies. A working heuristic: if a role list stops fitting on one screen, the model is absorbing work that belongs to another layer.
Designing roles: top-down, bottom-up, or both
The part no ranking explainer covers: where roles come from. Top-down derives them from the org and its processes — interview the business, name the functions, assign minimal permissions; accurate but slow. Bottom-up mines them from existing grants — cluster who already holds what, and candidate roles fall out; fast but launders past mistakes into policy. Practice is hybrid: mine for candidates, validate against functions, then apply the 80/20 test — a handful of broad roles for the organization’s bulk, exceptions handled by ACLs or conditions rather than boutique roles. And one implementation rule that outlives every reorg: code should check permissions, not role names — can('posts:publish'), not hasRole('Editor') — so redefining a role is a data change, not a refactor, with enforcement server-side, deny-by-default.
Common use cases
- Admin panels and back-offices — support, moderator, finance, superadmin: functions map cleanly to roles.
- Content and publishing workflows — author, editor, publisher, with separation between writing and releasing.
- B2B SaaS team permissions — owner/admin/member/billing per workspace, scoped per tenant.
- Compliance-bound operations — separation of duties as enforceable role constraints, with role membership as the audit artifact.
- The role-gate on data layers — roles as the “who” in class-level permissions and row-level policies.
Should you use RBAC? A decision matrix
| Situation | Lean |
|---|---|
| Access follows job function | RBAC — its home game |
| Users share individual records ad hoc | ACLs — roles can’t express it |
| Context decides (time, state, tenant) | Attribute conditions over the roles |
| A handful of user types, stable | RBAC with 3–7 broad roles |
| Deep org charts, nested teams | Role hierarchy — or ReBAC at real scale |
| ”Just admins and everyone else” | One role gate — don’t over-model it |
Limitations and trade-offs
- Per-object sharing is out of scope. “Share this document with Ana” has no RBAC answer short of a role per document — that decision belongs to ACLs.
- Same role, same powers. Two Editors are indistinguishable; individual nuance requires another layer, not a near-duplicate role.
- Role engineering is real upfront work. Skipping it yields roles that mirror neither the org nor the risk model — and get copied forever.
- Static roles miss dynamic risk. Role membership doesn’t see unusual hours, new devices, or sensitive record states; that’s attribute territory.
- Drift is the steady state. Without periodic recertification, role scope only grows; RBAC’s auditability is a capability, not a guarantee.
Roles on Back4app
Back4app is an open-source Backend-as-a-Service (BaaS) platform that combines a managed database, auto-generated REST and GraphQL APIs, authentication, file storage, and Cloud Code serverless functions. Roles here are data, not code: each is an object in the _Role class with a users relation for members and a roles relation for nesting — and nesting is hierarchy for free, since members of a child role inherit whatever its parent roles are granted. The grants themselves happen exactly where the composition section points: role names appear in class-level permissions for the broad strokes and in per-object ACL entries for the exceptions — the code tabs show both halves — with Back4app enforcing the result on every REST, GraphQL, and Live Query request. Because roles are queryable objects, membership management, audits, and admin UIs are ordinary database work: role explosion prevention as a data-modeling habit, not a governance project.
Frequently asked questions
What is RBAC in simple terms?
Access is granted by job function, not per person: permissions attach to roles — Editor, Admin, Support — and users inherit whatever their assigned roles carry. Hiring, promotion, and departure become role changes in one place instead of permission edits scattered everywhere.
What is an example of RBAC?
A content platform with three roles: Admin (everything), Editor (create, edit, publish), Viewer (read only). Ada the editor publishes because Editor carries publish — not because anyone granted Ada anything directly. Reassign her to Viewer and every editing permission vanishes in a single update.
What is the difference between RBAC and ABAC?
RBAC decides from predefined roles; attribute-based access control evaluates attributes of the user, resource, and context — department, sensitivity, time — at request time. RBAC is simpler to reason about and audit; ABAC is finer-grained and harder to debug. Mature systems use RBAC as the baseline and add attribute conditions where context genuinely matters.
What is the difference between RBAC and an ACL?
Direction of attachment: an ACL hangs subject-permission entries on each object; RBAC hangs permissions on roles across the system. They compose rather than compete — an ACL entry can name a role, which is how per-object control and one-place membership management coexist.
What are the RBAC models?
The standard defines core RBAC (users, roles, permissions, sessions), hierarchical RBAC (senior roles inherit junior roles' permissions), and constrained RBAC (separation-of-duty rules — static constraints on assignment, dynamic constraints on what one session may activate together).
What are the three rules of RBAC?
From the original 1992 formulation: a subject can act only through a selected role (role assignment); the subject must be authorized for that role (role authorization); and an action is allowed only if the active role holds its permission (permission authorization). Together: no access except through roles.
What is role explosion?
Uncontrolled role growth when every exception, project, region, or tenant spawns a new role — Project-A-Manager-Region-West — until roles outnumber users and nobody can audit the system. The root cause is encoding contextual attributes as roles instead of handling them with conditions or per-object entries.
Is RBAC the same as least privilege?
No — least privilege is the principle, RBAC is one mechanism for pursuing it, and only role discipline connects them. An over-broad role violates least privilege from inside RBAC; scoping roles minimally and reviewing them periodically is what actually delivers the principle.
What is separation of duties in RBAC?
Constraints that keep conflicting powers apart: static separation blocks one user from ever holding both payment-creator and payment-approver; dynamic separation allows holding both but never activated in the same session. It is fraud prevention expressed as a role rule.