Authorization Models: RBAC, ABAC & ReBAC
Use RBAC for coarse org roles, ReBAC/Zanzibar (relation tuples, graph checks, reverse indexes via OpenFGA/SpiceDB) when permissions are per-object with sharing and nesting; split PDP from PEP, deny by default and fail closed, and enforce a per-object check on every request to kill IDOR.
Authorization is per-object, and getting it wrong tops OWASP
Authentication answers "who are you"; authorization answers "are you allowed to do this to this specific object." Getting authorization wrong is the number one item on the OWASP API Security Top 10 (Broken Object Level Authorization, aka BOLA/IDOR), so interviewers probe it hard. The first decision is which model expresses your permissions.
RBAC, ABAC, ReBAC
RBAC (Role-Based Access Control) assigns users to roles (admin, editor, viewer) and roles to permissions. It is simple, auditable, and correct for coarse, org-wide access. Its failure mode is role explosion: the moment permissions depend on which object, you start minting roles like editor-of-folder-4821, and a company with a million folders needs a million roles. RBAC has no notion of "editor of that document."
ABAC (Attribute-Based Access Control) decides from attributes of the subject, resource, action, and environment ("allow if user.department == doc.department and time < 18:00"). It is expressive and great for compliance rules, but policies get hard to reason about and hard to answer the reverse question "who can see this doc?" because there is no stored relationship, just a function evaluated at request time.
ReBAC (Relationship-Based Access Control) models permissions as a graph of relationships between objects and users. This is what Google's Zanzibar paper formalized and what powers Drive, Docs, Calendar, and YouTube. Permissions are stored as relation tuples: object#relation@user, for example doc:readme#viewer@user:alice or doc:readme#parent@folder:eng. Relations compose: a folder's viewer can be inherited by every child doc via a userset rewrite ("a doc's viewer = its own viewers UNION its parent folder's viewers"). Groups are just more tuples: group:eng#member@user:alice, and doc:readme#viewer@group:eng#member grants the whole group. This naturally expresses sharing, nested folders, and org roles without role explosion. Open-source implementations are OpenFGA, SpiceDB (both Zanzibar-modeled), and AWS Cedar.
A Zanzibar-style system answers two query shapes: Check ("can alice view doc:readme?") walks the relationship graph, and Expand / reverse-index ("list every doc alice can view" or "list every user who can view this doc") which powers search filtering and share dialogs. It must return decisions in single-digit milliseconds because every request blocks on it.
You just met three authorization models. Match each statement to its model.
Separate the PDP from the PEP
Whatever model you pick, separate the Policy Decision Point (PDP) from the Policy Enforcement Point (PEP). The PEP lives in each service or gateway and asks the PDP "allowed?"; the PDP (OPA, Cedar, OpenFGA) owns the policy logic. Externalizing authz means one place to audit and change rules instead of if user.isAdmin scattered across 50 services.
request -> PEP (in service/gateway) --check(user, action, object)--> PDP (OpenFGA/OPA/Cedar)
|
relation tuples / policy + graph -+
Non-negotiable enforcement principles: deny by default, least privilege, fail closed (if the PDP is unreachable, reject, do not wave the request through). And enforce at every trust boundary and every object, not once at the front door.
Interview nuance: the classic wrong turn is treating authz as a single gate. A route checks user.isLoggedIn, then the handler does SELECT * FROM docs WHERE id = :id with the id straight from the URL, never checking that this user may see that doc. That is IDOR/BOLA. The fix is a per-object check on every access: check(user, "view", doc) before returning it. Zanzibar also has a subtle consistency problem, the "new enemy" problem: if you remove someone's access and then change the object, a stale cache could let the just-removed user read the new content. Zanzibar solves it with zookies, opaque consistency tokens that pin a check to a snapshot at or after the ACL change.
Recap: use RBAC for coarse org roles, ReBAC/Zanzibar (relation tuples, graph checks, reverse indexes via OpenFGA/SpiceDB) when permissions are per-object with sharing and nesting; split PDP from PEP, deny by default and fail closed, and enforce a per-object check on every request to kill IDOR.
Apply
Your turn
The task this lesson builds to.
Design the permission system for a Google-Drive-style app with per-file sharing, groups, nested folders, and org roles.
Think about
- When does RBAC hit role explosion, and when does ReBAC fit?
- How does the Zanzibar model represent permissions?
- How do you avoid IDOR / broken object-level authorization?
Practice
Make it stick
A second problem on the same idea, so it survives past today.
Design the authorization layer for a GitHub-scale code host: 100M+ repositories, personal accounts and organizations, teams with nested subteams, per-repo roles (read/triage/write/maintain/admin), outside collaborators, and branch-protection rules, all authorized on every git and API operation with a sub-10ms p99 budget.
Think about
- How do you model the five per-repo roles as an implication chain rather than five booleans?
- How do nested subteams and three grant sources compose in a Check?
- Why is branch protection a policy overlay rather than pure ReBAC?