system-design

Authentication vs Authorization

Two words that sound alike, are constantly confused, and answer completely different questions: who are you? and what may you do?

Prerequisites: Client–Server Model, API Gateway Time to read: ~18 minutes


The two questions

AuthN:  "You are Bilal."                          (identity established)
AuthZ:  "Bilal may read this document
         but not delete it, and may not see
         anyone else's documents."                (permissions checked)

🚨 They happen in that order and are separate steps. You authenticate once (establish who), then authorize every action (check what). Conflating them — or doing one and skipping the other — is the root of a large fraction of security bugs.

The mnemonic: autheNtication = who, authoRization = permissions (rights).


Authentication: proving who you are

Three factors, and combining them is multi-factor authentication (MFA):

Factor Example Strength
Something you know Password, PIN Weak alone (phishable, reused, guessable)
Something you have Phone (TOTP), security key, hardware token Strong
Something you are Fingerprint, face Convenient; can’t be changed if compromised

🚨 Passwords alone are the weakest option, because they’re reused across sites, phished, and breached in bulk. MFA — especially a hardware key or an authenticator app — defeats the vast majority of account takeovers, which is why it’s the single highest-value security control you can add.

How passwords must be stored (this comes up constantly):

❌ Plaintext                    — a breach exposes every password
❌ Encrypted                    — reversible; the key gets stolen too
❌ MD5 / SHA-256 (fast hash)    — GPUs crack billions/second → broken
✅ bcrypt / scrypt / Argon2     — slow, salted, memory-hard hashes

🚨 Passwords are hashed, never encrypted, and with a slow, salted algorithm designed to resist brute force. A fast hash (SHA-256) is crackable at billions of guesses per second on a GPU; bcrypt/Argon2 are deliberately slow and memory-hard. The salt (a unique random value per password) defeats precomputed rainbow tables and means two users with the same password get different hashes. → Encryption, Sessions/JWT/OAuth

Passwordless and modern auth: passkeys (WebAuthn/FIDO2) replace passwords with device-bound cryptographic keys — phishing-resistant and increasingly the direction the industry is moving. Worth knowing as the current best practice.


Authorization: checking what you can do

Once identity is established, every action needs a permission check. The models, from simple to flexible:

RBAC — Role-Based Access Control

Users have roles; roles have permissions.

User "Bilal" → role "editor" → permissions [read, write]  (but not delete or admin)

✅ Simple, the most common model, easy to reason about and audit. ❌ 🚨 Role explosion — as needs get granular (“editor for this project but viewer for that one”), you end up with hundreds of roles. RBAC struggles with per-resource, per-context permissions.

ABAC — Attribute-Based Access Control

Decisions based on attributes of the user, resource, action, and environment.

Allow if:  user.department == resource.department
       AND action == "read"
       AND environment.time is business_hours
       AND user.clearance >= resource.sensitivity

✅ Extremely flexible; handles context (time, location, resource ownership) RBAC can’t. ❌ Complex to reason about, harder to audit (“who can access this?” is a hard query), and easy to misconfigure.

ReBAC — Relationship-Based Access Control

Permissions derived from relationships in a graph. “You can edit a document if you own it, or if someone who owns it shared it with you.”

🚨 This is Google’s Zanzibar model, which powers permissions across Google Drive, YouTube, and more, and inspired open-source systems (SpiceDB, OpenFGA, Ory Keto). It’s the modern answer for complex sharing/collaboration permissions — “who can access this doc, through any chain of sharing?” is a graph traversal. → Graph Databases

🎙️ “RBAC for coarse role-based access, but for a collaboration product with per-document sharing I’d use a relationship-based model like Zanzibar — ‘can this user access this resource through any sharing chain?’ is a graph problem RBAC handles badly.”

The three together

Model Decides by Best for
RBAC Roles Most applications; coarse access
ABAC Attributes + context Fine-grained, contextual rules
ReBAC Relationships Sharing, collaboration, hierarchies

Where authZ goes wrong: the most common vulnerability

🚨 Broken access control is #1 on the OWASP Top 10, and the specific failure is almost always the same: checking authentication but forgetting authorization on a specific action.

IDOR — Insecure Direct Object Reference:

# ❌ Authenticated, but no ownership check
@app.get("/orders/{order_id}")
def get_order(order_id, current_user):
    return db.get_order(order_id)      # ANY logged-in user can read ANY order

# ✅ Authorize the specific resource
@app.get("/orders/{order_id}")
def get_order(order_id, current_user):
    order = db.get_order(order_id)
    if order.user_id != current_user.id:
        raise Forbidden()              # or 404, to not confirm it exists
    return order

🚨 The bug is subtle because the code looks secure — the user is logged in, there’s a current_user. But being authenticated isn’t being authorized for this specific object. Change the ID in the URL and read someone else’s data. This is one of the most common real-world breaches, and sequential IDs make it trivial to exploit.

The rule: authorize every resource access, not just the endpoint. Authentication at the door; authorization at every object.


Where each check lives

🚨 A common interview point (from the gateway chapter):

🚨 And the trap: if the gateway passes identity as headers (X-User-Id) and a service trusts them blindly, anyone who can reach the service directly can impersonate any user by setting the header. Mitigate with network isolation, stripping inbound identity headers at the gateway, signed internal tokens, or mTLS. → Service Mesh


Principle of least privilege

🚨 The foundational security principle: grant the minimum access needed, nothing more. Applies everywhere:

Why it matters: it limits blast radius. When something is compromised — and something eventually is — least privilege determines whether the attacker gets one bucket or your whole infrastructure. Combined with zero trust (assume the network is hostile, authenticate everything), it’s the backbone of modern security architecture.


Machine-to-machine auth

Not everything is a human logging in. Services authenticating to each other:


⚖️ Trade-offs

Choice Gain Cost
MFA Defeats most account takeover User friction; recovery flows
RBAC Simple, auditable Role explosion for fine-grained needs
ABAC Flexible, contextual Hard to reason about and audit
ReBAC (Zanzibar) Handles complex sharing A permissions service to operate
AuthN at gateway One place; services simplified Services must not blindly trust identity headers
Least privilege Small blast radius More granular access management

In the real world


🚨 Interview traps


🎙️ Soundbites


🛠️ Try it

1. Exploit an IDOR, then fix it. Build an endpoint GET /orders/{id} that requires login but doesn’t check ownership. Log in as user A, then request user B’s order by changing the ID. Watch it succeed. Add the ownership check and watch it 403. This is the single most important security exercise in the chapter — the bug that tops the OWASP list, felt directly.

2. Crack a fast hash vs a slow one. Hash a list of common passwords with SHA-256 and with bcrypt. Time how fast you can brute-force each with a wordlist. The difference — millions/second vs a handful — is why password hashing must be slow.

3. Prove salts matter. Hash the same password for two users without a salt (identical hashes — visible in a breach) and with a salt (different hashes). Now a breach doesn’t reveal which users share a password.

4. Model permissions three ways. For a document-sharing app, model “who can edit this doc” as RBAC, then ABAC, then ReBAC. Notice where RBAC forces you into role explosion and where ReBAC’s graph model fits naturally.


Check yourself

1. What's the difference between authentication and authorization, and why does the order matter? Authentication answers "who are you?" — proving identity (logging in). Authorization answers "what are you allowed to do?" — checking permissions for a specific action or resource. The order matters because you must know *who* before you can decide *what they may do*: authentication happens once to establish identity, then authorization happens on *every* action to check that this identity is permitted to do this specific thing. The most common security bug is doing the first and skipping the second on a particular action — a user is logged in (authenticated) but the code forgets to check they own the resource they're accessing (authorization), so they can read anyone's data.
2. Why are passwords hashed rather than encrypted, and why with a slow algorithm? Hashed rather than encrypted because encryption is *reversible* — if the encryption key is stolen (and in a breach that exposes the password store, the key is often nearby), every password is recovered. Hashing is one-way: you store the hash, and at login you hash the submitted password and compare, never needing to reverse it. Slow (bcrypt, scrypt, Argon2) rather than fast (MD5, SHA-256) because attackers who steal the hashes brute-force them offline — a fast hash lets a GPU try billions of guesses per second, cracking weak passwords in minutes, while a deliberately slow, memory-hard hash makes each guess expensive enough to make mass cracking impractical. Combined with a per-password salt, this defeats rainbow tables and ensures identical passwords produce different hashes.
3. What is IDOR and why is it so common? Insecure Direct Object Reference: an endpoint checks that the user is authenticated but not that they *own* or may access the specific object they're requesting, so changing an identifier in the URL (`/orders/123` → `/orders/124`) returns another user's data. It's the most common form of broken access control (OWASP #1) because the vulnerable code *looks* secure — there's a logged-in user and a `current_user` object, so it passes casual review — but authentication isn't authorization for a specific resource. It's especially easy to exploit when IDs are sequential and guessable. The fix is to authorize every resource access: after loading the object, verify the current user is permitted to access *that object*, returning 403 (or 404 to avoid confirming existence) otherwise.
4. Where should authentication and authorization each live in a microservices system? Authentication belongs at the edge — validate the token once at the API gateway and pass verified identity downstream, so services don't each reimplement token validation. Coarse-grained authorization (does this role have access to this route at all?) can also live at the gateway. Fine-grained authorization (does *this specific user* own *this specific order*?) must live in the service, because only the service has access to the business data that establishes the relationship — the gateway can't know that user 42 owns order 993 without querying the domain. The critical caveat: if the gateway passes identity as headers, services must not blindly trust them — a caller who reaches a service directly could set `X-User-Id` to impersonate anyone. Defend with network isolation, header stripping at the gateway, signed internal tokens, or mTLS.
5. When would you choose ReBAC (Zanzibar) over RBAC? When permissions derive from *relationships* rather than fixed roles — especially collaboration and sharing scenarios. RBAC assigns roles with permissions, which works well for coarse access ("editors can edit") but explodes into hundreds of roles when access is per-resource and per-context ("editor for this document because someone shared it with you, viewer for that one, owner of a third"). ReBAC models permissions as a graph of relationships — user owns document, document shared-with user, user member-of group, group has-access-to folder — and a permission check becomes a graph traversal ("is there any path from this user to this resource granting edit?"). Google's Zanzibar powers Drive and YouTube this way, and it's the right model for any product where users grant each other access to resources through chains of sharing.

Further reading