AppCrib
Developer Tools

RBAC0 Through RBAC3: What the NIST Standard Actually Defines

Domain knowledge·Published by AppCrib··
RolematBuild an RBAC matrix. Export it anywhere.

The first time anyone documents access control for a SOC 2 readiness review, the auditor asks a question with no good off-the-cuff answer: "Is your RBAC model flat, hierarchical, or constrained?" Most engineering teams pause. They built roles, they assign permissions to roles, they call it RBAC, and that is where their mental model stops. The NIST standard has four levels of RBAC and the question matters for compliance, for code review, and for whether the documentation everyone is about to write is honest about what the system actually does.

Most teams document something they think is RBAC1 or RBAC2 but actually run RBAC0. Untangling which level you are at requires reading the standard, which means it almost never happens.

Where the Four Levels Come From

The four-level model traces back to Ravi Sandhu's 1996 paper "Role-Based Access Control Models" in IEEE Computer (Volume 29, Issue 2). That paper defined RBAC0, RBAC1, RBAC2, and RBAC3 as a family. RBAC0 is the base case. RBAC1 adds role hierarchies. RBAC2 adds constraints. RBAC3 combines both.

NIST proposed a unified model in 2000 building on the same skeleton. INCITS 359 was ratified as the American National Standard for RBAC in 2004 and revised in 2012, retaining the same conceptual layers: Core RBAC (matches RBAC0), Hierarchical RBAC (matches RBAC1), Static Separation of Duties (a constraint type from RBAC2), Dynamic Separation of Duties (another constraint type from RBAC2), and Symmetric RBAC as the union (matches RBAC3).

The terminology drifts between the academic paper and the standard. Internally, most teams use Sandhu's RBAC0/1/2/3 names because they are shorter. Compliance documentation tends to use the NIST names because that is what shows up in SOC 2 cross-walks and ISO/IEC 27001 Annex A.9 mappings. Both vocabularies refer to the same four conceptual layers.

RBAC0: The Flat Matrix

RBAC0 has three elements and one relation. Users get assigned to roles. Permissions get assigned to roles. A user's effective permissions are the union of permissions across all their roles. That's it. No hierarchy, no inheritance, no exclusion rules, no session machinery beyond "the user is logged in as some subset of their roles."

The flat-matrix representation, with roles across the top and permissions down the side and a check in each cell, is the natural visualization of RBAC0 and only RBAC0. The minute you introduce role hierarchies or constraints, the matrix stops telling the whole story and starts misleading whoever reads it.

Most production web applications run RBAC0 in the code and don't realize it. The role table has rows. The permission table has rows. There's a join table connecting them. Nobody ever wrote parent_role_id or added a constraint check. The system is flat by accident. Flat is enough for most apps, but it helps to know that's what you actually have before you tell an auditor otherwise.

RBAC1: Role Hierarchy and Inheritance

RBAC1 adds the idea that one role can inherit permissions from another. admin inherits everything editor can do. editor inherits everything author can do. The hierarchy is a partial order, not a strict tree; a single role can have multiple parents because multiple inheritance is permitted in the standard.

Two distinct flavors exist: general hierarchies (arbitrary partial order, multiple inheritance) and limited hierarchies (single-parent inheritance, simpler to reason about). Most ORMs and policy engines that advertise "RBAC with role inheritance" implement the limited form because the general form makes effective-permission computation more expensive at runtime and harder to debug when an audit asks why a specific user can do a specific thing.

The inheritance trap that catches people: if editor has delete:posts and you accidentally make author inherit from editor (the wrong direction), then every author can delete every post and nobody notices until the audit. The convention is that more-privileged roles inherit from less-privileged ones, not the other way around. The standard doesn't enforce the direction; it's a code review concern. A flat checkbox matrix can't represent the inheritance edge, which is why teams with real hierarchies usually need a supplementary diagram alongside the matrix.

RBAC2: Constrained RBAC and Separation of Duties

RBAC2 introduces constraints: rules about which role assignments are forbidden. The headline constraint type is separation of duties, and the standard distinguishes two kinds.

Static separation of duties (SSD): a user can't be assigned to both role A and role B, ever. The classic case is requester and approver on the same purchase order. The system refuses the assignment at user-management time, before the user even logs in.

Dynamic separation of duties (DSD): a user can be assigned to both roles but can't activate both in the same session. The user picks which hat they're wearing. The system enforces the boundary per session, not per assignment.

SoD constraints exist mostly to satisfy compliance frameworks. SOX 404 expects SSD between transaction-initiation and transaction-approval roles. HIPAA 164.308(a)(3) on workforce clearance procedures lands on similar territory for clinical-versus-administrative roles. SOC 2 CC6.3 (logical access controls based on least privilege) is frequently answered with an SSD list and a screenshot of the role assignment UI refusing a forbidden combination.

Documenting RBAC2 takes more than a checkbox matrix. You need the matrix of role-to-permission assignments and a separate constraint table listing forbidden role pairs (for SSD) or forbidden activation combinations (for DSD). Most teams discover this halfway through writing their compliance package, when they realize "Bob is in finance and AP-approver" doesn't show up in the role matrix at all because the matrix says nothing about which users hold which roles or which combinations are forbidden.

RBAC3: Symmetric, Hierarchies With Constraints

RBAC3 is the combination: role hierarchies from RBAC1 plus constraints from RBAC2. The standard calls this Symmetric RBAC because the hierarchy and the constraint set both apply, and they have to be consistent with each other.

The consistency problem is non-trivial. If senior-engineer inherits from engineer and there's an SSD constraint that says you can't hold engineer and auditor simultaneously, does the constraint apply to senior-engineer too? The standard says yes; inheritance pulls in the constraint along with the permissions. Most home-grown RBAC implementations get this wrong because the constraint check looks at the directly assigned roles rather than the transitively inherited ones, and the bug only surfaces when somebody gets promoted into a role that violates a constraint nobody noticed.

Almost nobody runs full RBAC3 in production application code. Enterprise IAM platforms (SailPoint, Saviynt, Okta Workflows) support it because that's their job. The average web application implementing "RBAC" is somewhere between RBAC0 and RBAC1, with one or two informal constraints checked in application code rather than declared in an auth policy.

How to Tell Which Level You Are Actually Running

Run the questions in order. The first "no" answers the question.

QuestionIf yes, you are atIf no
Do you have a parent_role_id (or equivalent) and runtime code that walks it to compute effective permissions?RBAC1 or higherYou are at RBAC0
Do you reject user-to-role assignments based on rules about other roles the user already holds?RBAC2 or higher (SSD)You are at RBAC1
Do you reject role activation in a session based on roles already activated in the same session?RBAC2 or higher (DSD)You are at RBAC1 or SSD-only RBAC2
Do both the inheritance check and the constraint check consider transitively inherited roles?RBAC3You are at RBAC1 plus partial RBAC2

Most teams stop at the first question, discover they're RBAC0, then go back and rewrite their documentation to stop claiming "RBAC with role hierarchies." The mismatch is often surprising. The system was simpler than the documentation claimed, which is the opposite of the usual drift direction.

A small but real percentage of teams discover they're running RBAC2 without realizing it because their UI rejects certain role assignments but their documentation doesn't list the constraints. The constraint exists in dropdown logic, not in the access policy. That's the kind of finding an auditor circles in red ink, because logic that lives only in the UI isn't enforced when somebody calls the API directly.

What the Standard Will Not Tell You

The standard is silent on several things that come up immediately in implementation.

Naming conventions for permissions aren't specified. Whether you write posts.delete, delete:posts, delete_post, or Posts.Delete is up to you. The standard treats permissions as opaque strings; the only invariant is that the same string in your documentation and in the auth-check function must refer to the same operation. Documentation drift in this corner is the most common bug in any RBAC system, and the matrix is usually where the drift becomes visible.

Resource scoping isn't part of core RBAC. The standard treats delete:posts as a permission, not delete:posts where author = current_user. Per-instance access control (the user can delete their own posts but not anyone else's) is technically a different model (ABAC or ReBAC, depending on which textbook you read) and it gets bolted onto RBAC in most real applications. A flat matrix doesn't show it. A note next to the matrix has to.

Session management and time-bound permissions are out of scope. NIST RBAC says nothing about role activation windows, time-of-day restrictions, or break-glass access. Those end up in the auth layer or the policy engine, documented separately from the role-to-permission matrix.

Tooling for keeping the documented matrix synchronized with the enforced matrix is also out of scope. The standard defines the model; making sure your README reflects what your code does is a process problem, not a standards problem. That gap is why a permission matrix in a README needs to get re-reviewed on a cadence. Quarterly is the typical SOC 2 expectation under CC6.1 evidence requirements, usually paired with a screenshot of the most recent review meeting in the audit packet.

For most teams documenting access control today, the honest deliverable is a clean RBAC0 matrix, a short note about any role hierarchy that exists in the code, and a separate constraint list if separation-of-duties applies. If you need to put one together quickly, Rolemat handles the matrix portion (paste roles, paste permissions, toggle the grid, export Markdown), and the hierarchy note and constraint list go directly underneath in the same README section.

Rolemat
Build an RBAC matrix. Export it anywhere.
Try Rolemat