Skip to main content

Authorization models

Authorization answers one question: may this subject perform this action on this object? The four common models — ACL, RBAC, ABAC and ReBAC — all answer it. They differ in where the answer is stored, and that difference decides which questions stay cheap as the system grows. This page is deliberately product-free. It describes the field, not AgentArea. The model AgentArea actually deploys is described in the AgentArea model, and you cannot evaluate that page without this one.

The problem

Every system starts with permissions inline: an if user.is_admin here, a hardcoded list of allowed emails there. That works until three things happen at once.
  • Sharing arrives. One user wants to give another user access to one object, without making them an admin of everything.
  • Hierarchy arrives. Objects live inside containers, and access to the container should reach the contents — without copying a grant onto every child.
  • Someone asks the reverse question. Not “may Alice read this?” but “what can Alice read?” and “who can read this?” Inline checks answer the first and are useless for the other two.
An authorization model is the decision about which of those three you make cheap.

The four models

ACL

An access control list attaches permissions to the object: a list of (subject, permission) pairs stored with the thing being protected. Unix file modes and S3 bucket ACLs are the canonical examples. ACLs are exact and obvious. They are also flat: there is no way to say “everyone in Finance” without expanding Finance into individual entries, and no way to say “grants on this folder reach its files” without writing the grant on every file. An org-wide policy change means rewriting every list.

RBAC

Role-based access control bundles permissions into named roles — editor, billing-admin — and assigns roles to subjects. The permission set moves from the object to the subject. RBAC answers “what may this person do” in one lookup, which is why it dominates enterprise IAM. Its failure mode is specific and well known: role explosion. The moment permissions need to differ per object, you need a role per object (project-42-editor), and the role catalogue grows with the data. Most systems that “use RBAC” for sharing have quietly reinvented ACLs inside their role names. Two role shapes exist and they are not interchangeable. Concentric roles nest (manage implies write implies read) — GitHub and Google Drive work this way. Independent bags do not: a write-only role grants write and nothing else. Concentric is easier to reason about; independent is what you need when “can modify but must not read the contents” is a real requirement.

ABAC

Attribute-based access control computes the decision at request time from attributes of the subject, the object and the environment: department, data classification, time of day, source network, spend so far. It is the most expressive model — it is the only one that can express “not after hours” or “only while under budget”. The cost is that decisions are computed, not stored. You cannot answer “who can read this document” without evaluating the policy against every subject, and you cannot audit an ABAC system by reading its data — you have to read its rules, which are effectively code. Policy languages like Rego and CEL exist because those rules need a reviewable form.

ReBAC

Relationship-based access control stores the edges between objects and derives permissions by walking them. Alice can read doc:1 because she is an editor of folder:9, and doc:1 sits in folder:9. Nothing about that permission is stored on doc:1; it is computed from two relationships. The design comes from Google’s Zanzibar paper and is implemented by OpenFGA, SpiceDB and Ory Keto. It is the model behind Drive-style, Notion-style and Figma-style sharing, and it answers all three questions:
  • Check — may this subject do this to this object (walk the graph).
  • List objects — what can this subject reach (reverse lookup from the subject).
  • List subjects — who can reach this object (expand from the object).
The cost is real. The graph is a second datastore that must stay consistent with your primary database, every write is a distributed write, and the graph on its own has no notion of time, budget or request context. Production ReBAC systems add conditional edges (OpenFGA calls them conditions) precisely to buy back a slice of ABAC.

When each fits

  • Reach for ACL when objects are few, sharing is direct, and nobody asks the reverse question. It is not a beginner’s mistake; it is a correct answer to a small problem.
  • Reach for RBAC when permissions describe job functions rather than objects: who may access the billing system at all, who may deploy.
  • Reach for ABAC when the decision genuinely depends on request context — time, spend, classification, network — and you accept that you will not be able to enumerate access from stored data.
  • Reach for ReBAC when objects nest and users share individual objects with each other. If your product has folders, projects or workspaces and a Share button, this is the shape of your problem.
Note that ReBAC and RBAC are not opposites. A ReBAC graph can hold roles as objects — a role node carrying permission bits, bound to a subject and a target by an assignment node. That is how you get role bundles without role explosion: the role names the bundle, the graph edge names the scope.

Why not RBAC alone

RBAC is the default answer and the reason to reject it is specific, not stylistic: roles are global, sharing is local. A role is a bundle of permissions that means the same thing everywhere it is assigned. The moment a user needs edit on one project and read on another, the role has to carry the project in its identity, and you are maintaining a role catalogue that grows with your data rather than with your job functions. Systems that go down this path end up with thousands of roles nobody can audit, and the reverse question — who actually has access to this project — becomes a string search over role names. ReBAC keeps the bundle and moves the scope onto an edge. The same role object can be attached to a hundred projects with a hundred assignment edges, and the graph still answers both directions in one query. The tradeoff you accept in exchange is an extra datastore with its own consistency and availability characteristics.

Limits

  • A model is data, not enforcement. ACL, RBAC, ABAC and ReBAC all describe how a decision is derived. None of them tells you where the check is called. A perfectly modelled graph with no call site at the execution path authorizes nothing, and this is by far the most common way authorization projects fail.
  • Listing is a separate engineering problem. ReBAC gives you reverse lookup in principle. In practice, list endpoints usually keep the database as the pagination and sorting backbone and refine a page with batched checks, because reverse lookup cannot sort by business fields and has result caps. Expect to build that layer.
  • No model decides the failure posture. When the authorization store is unreachable, the system either denies (fail-closed, correct, causes an outage) or allows (fail-open, keeps serving, is a security incident). That is an operational choice made in code, and a system that has not made it explicitly has made it by accident.
  • Hybrids are the norm, not a compromise. Relationship graphs handle hierarchy and sharing; runtime ceilings such as budgets, rate limits and approval requirements are attribute-shaped and belong to a policy engine. A system with both is not confused — it is answering two different questions.