Authorization
Authentication gave you a trustworthy name. Authorization decides what that name is permitted to do. The two are sequential and must never be merged: a system can know exactly who you are and still — correctly — refuse to let you delete the production database. Every “403 Forbidden” you’ve ever seen is authorization saying “I believe you’re you; you still can’t do that.”
The question authorization answers
Section titled “The question authorization answers”Reduced to its core, authorization evaluates a single predicate:
can SUBJECT perform ACTION on RESOURCE ?
"can Alice DELETE document #42 ?" → yes / noEverything else — roles, policies, permission tables — is just different machinery for computing that yes/no quickly, correctly, and maintainably. The models differ in where the rule lives and how it scales as subjects, actions, and resources multiply.
Three models, three shapes of rule
Section titled “Three models, three shapes of rule”ACLs — permissions attached to the resource
Section titled “ACLs — permissions attached to the resource”An Access Control List hangs a list of “who can do what” directly on each resource. A file says: Alice can read+write, Bob can read. This is intuitive and precise — perfect for per-object sharing (think Google Docs “share with specific people”). But it doesn’t scale to organizations: granting a new hire the same access as their team means editing thousands of ACLs by hand.
document #42 → { Alice: read,write ; Bob: read } document #43 → { Alice: read ; Carol: read,write }RBAC — permissions attached to roles
Section titled “RBAC — permissions attached to roles”Role-Based Access Control inserts a layer: users get roles, roles get permissions. You no longer grant abilities to people; you grant them to roles and assign people to roles.
Alice ─► role:editor ─► { read, write, publish } Bob ─► role:viewer ─► { read }This is the workhorse of enterprise systems because it matches how organizations actually think (“she’s an admin,” “he’s on support”). The cost is role explosion: real permissions depend on context (“editors, but only for their own region”), and encoding every combination as a separate role produces hundreds of nearly-identical roles nobody can audit.
ABAC — permissions computed from attributes
Section titled “ABAC — permissions computed from attributes”Attribute-Based Access Control decides at request time by evaluating a policy over attributes of the subject, resource, action, and environment:
ALLOW if subject.department == resource.department AND action == "read" AND environment.time is business_hoursABAC is enormously expressive — it captures rules RBAC can’t without exploding — but the cost is complexity and auditability: a policy engine is harder to reason about, and “why was this allowed?” can require replaying the whole evaluation. Many mature systems blend the two: roles for the coarse strokes, attributes for the fine print.
Least privilege, made concrete
Section titled “Least privilege, made concrete”The governing principle from the overview lands hardest here. Least privilege means every subject holds the minimum authority to do its job — and authorization is where you encode that. Two practices make it real:
- Default deny. The base case is no. Access is granted by explicit rule, never assumed. A rule you forgot to write should fail closed (deny), not fail open (allow). An “allow unless blocked” system leaks the moment someone forgets to block.
- Deny by absence, not by patching. Don’t grant broad access and then carve out exceptions; grant narrow access and add capabilities as genuinely needed. The first approach forgets an exception and leaks; the second forgets a grant and merely inconveniences someone — a far safer failure.
What does least privilege buy us, and what does it cost? It buys a small blast radius: a stolen credential or a compromised service can only reach what it was narrowly allowed. It costs ongoing maintenance — narrow grants need updating as roles change — and the occasional “I can’t access the thing I need” friction. That friction is the sound of the system working.
Enforce at the right layer
Section titled “Enforce at the right layer”A rule is only as strong as its weakest enforcement point. The cardinal error is checking authorization in the UI — hiding a “Delete” button — and calling it secure. The button is a courtesy; the attacker doesn’t use your UI, they call your API directly.
UI → hides options (UX only — NEVER a security control) API gateway → coarse checks (is this token allowed to hit this route?) service layer → the real check (can THIS subject do THIS action on THIS resource?) database → last-ditch (row-level security) — defense in depthThe authoritative check belongs at the service layer, where you know the actual subject, action, and specific resource. Gateway checks are a useful coarse filter and database row-level security is a valuable backstop, but the decision “can Alice delete document #42” can only be made where #42 and Alice are both in scope.
Under the hood — the fourth model: relationships (ReBAC)
Section titled “Under the hood — the fourth model: relationships (ReBAC)”RBAC, ABAC, and ACLs aren’t the whole story. A fourth model has quietly become the backbone of large
consumer products: Relationship-Based Access Control (ReBAC), where permission is derived from a
graph of relationships — “Alice may view document #42 because she’s a member of a group that the
document’s parent folder is shared with.” Instead of a role or a policy, you store edges
(user→group, group→folder, folder→doc) and answer a request by walking the graph.
The canonical real implementation is Google Zanzibar, the system behind sharing in Google Docs, Drive, Calendar, and YouTube (its design was published in 2019). It answers enormous volumes of “can X do Y on Z?” checks by precomputing and caching reachability through that relationship graph. The open-source ecosystem it inspired — SpiceDB, Ory Keto, OpenFGA — has made ReBAC a practical default for anything with nested sharing. It buys you per-object sharing that also scales like roles; it costs you a dedicated authorization service and the discipline of keeping the relationship graph in sync with reality.
The thread
Section titled “The thread”Authorization is least privilege made executable. Authentication told you who crossed the trust boundary; authorization decides how far in they may go — and it must decide at the layer where the real subject and the real resource are both known, defaulting to deny when in doubt. Get this right and a compromised account is a contained incident; get it wrong and one valid login becomes the keys to everything. Next, see how identity and permission travel between systems with OAuth & JWT →.
The architect’s lens
Section titled “The architect’s lens”Authorization is least privilege made executable. Run the model choice through the five questions:
- Why does it exist? Because a trustworthy name isn’t permission — a system can know exactly who you are and still need to refuse the action. Every 403 is authorization computing
can SUBJECT do ACTION on RESOURCE? - What problem does it solve? Containing the blast radius of any one identity. Least privilege plus default-deny means a stolen credential or compromised service reaches only what it was narrowly granted — and the model (ACL / RBAC / ABAC / ReBAC) decides where the rule lives as subjects and resources multiply.
- What are the trade-offs? ACLs are precise per-object but don’t scale to orgs; RBAC matches job functions but suffers role explosion when context matters; ABAC is expressive but hard to audit (“why was this allowed?” means replaying the policy). Most mature systems blend them — roles for coarse strokes, attributes for the fine print.
- When should I avoid it (a given model)? Don’t reach for ABAC when access maps cleanly to job functions (RBAC is simpler and auditable); don’t enforce in the UI — hiding a button is UX, the attacker calls your API directly. The authoritative check belongs at the service layer where the subject and the specific resource are both in scope.
- What breaks if I remove it? Authentication alone becomes the keys to everything — one valid login can touch every record. The classic failure is IDOR: an endpoint authenticates the user but fetches
document/{id}without checking ownership (USPS 2018, ~60M records). Default-allow leaks the moment someone forgets to block.
Check your understanding
Section titled “Check your understanding”- Write the core authorization predicate (subject/action/resource) and apply it to one real example.
- Contrast ACLs, RBAC, and ABAC by where the rule lives and the main cost of each.
- Why is “default deny” safer than “default allow,” in terms of what a forgotten rule does?
- Why is hiding a button in the UI not an authorization control, and which layer is authoritative?
- Describe an IDOR vulnerability and the exact check that prevents it.
Show answers
can SUBJECT perform ACTION on RESOURCE?→ yes/no. For example, “can AliceDELETEdocument #42?” Everything else — roles, policies, permission tables — is just machinery for computing that predicate quickly and correctly.- ACLs put the rule on the resource (a list of who-can-do-what per object) — great for per-object sharing, but it doesn’t scale (a new hire means editing thousands of ACLs). RBAC puts it on roles (users get roles, roles get permissions) — matches how orgs think, but suffers role explosion when context matters. ABAC computes the rule from attributes of subject/resource/action/environment at request time — enormously expressive, but costs complexity and auditability.
- Default deny fails closed: a rule you forgot to write results in denial, which merely inconveniences someone. Default allow fails open: a forgotten rule leaks access. “Allow unless blocked” springs a leak the moment someone forgets to block; “deny unless granted” merely forgets a grant — a far safer failure.
- The attacker doesn’t use your UI — they call your API directly, so hiding a button is UX, not security. The authoritative check belongs at the service layer, where the actual subject, action, and specific resource are all in scope (the gateway does coarse route checks; database row-level security is a backstop — defense in depth).
- An IDOR (Insecure Direct Object Reference) is an endpoint that authenticates the user but fetches
document/{id}from the URL without checking that this user owns that document — authentication passed, authorization was skipped. The fix: always verify the subject’s relationship to the specific resource (“does Alice own #42?”), never just that they’re logged in.