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
- Authentication (authN): Who are you? Proving identity. Logging in.
- Authorization (authZ): What are you allowed to do? Checking permissions.
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):
- Authentication belongs at the edge — validate the token once at the
API gateway and pass verified identity downstream.
- Coarse authorization (does this role have access to this route at all?) can also live at the
gateway.
- Fine-grained authorization (does this user own this order?) must live in the service,
because only the service knows the data relationships. The gateway can’t know that user 42 owns
order 993 without querying business data.
🚨 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:
- Users get only the permissions their job requires.
- Services get scoped credentials (this service can read this bucket, not all of S3).
- Database accounts get only the tables and operations they need (the read replica’s account can’t
write).
- API tokens get the narrowest scopes.
- Grant access temporarily and revoke it when no longer needed (just-in-time access).
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:
- API keys — simple, but long-lived and hard to rotate; scope them tightly.
- OAuth client credentials — a service gets a token via its own credentials.
→ OAuth
- mTLS — both sides present certificates; standard in a service mesh. → Encryption
- Workload identity (SPIFFE/SPIRE, cloud IAM roles) — 🚨 the modern approach: a service’s identity
is tied to where it runs and it gets short-lived, automatically-rotated credentials, with no
long-lived secret to leak. This is the direction to prefer.
⚖️ 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
- Google Zanzibar is the reference for planet-scale relationship-based authorization, handling
permission checks for Drive, YouTube, Photos, and more. Its open-source descendants (SpiceDB,
OpenFGA) brought ReBAC to everyone, and it’s the standard answer for collaboration-product
permissions.
- IDOR / broken access control tops the OWASP list because it’s so common and so easy to
introduce — a single endpoint missing an ownership check exposes every record. Real breaches
(accessing other users’ accounts by changing an ID) happen constantly.
- Passkeys / WebAuthn are being adopted by Apple, Google, and Microsoft as the password
replacement, because they’re phishing-resistant by design — the credential is bound to the site and
the device and can’t be captured by a fake login page.
🚨 Interview traps
- Confusing authN and authZ. Who vs what.
- Encrypting passwords instead of hashing them with a slow, salted algorithm.
- Using a fast hash (MD5/SHA-256) for passwords.
- Checking authentication but not per-resource authorization (IDOR). The #1 vulnerability.
- Putting fine-grained authorization in the gateway — it can’t know ownership.
- Services trusting identity headers without isolation.
- Not applying least privilege to services and credentials.
- No MFA on anything sensitive.
🎙️ Soundbites
- “Authentication establishes who you are, once; authorization checks what you may do, on every
action. Conflating them is where most access-control bugs come from.”
- “Passwords are hashed with bcrypt or Argon2 — slow, salted, memory-hard — never encrypted and never
with a fast hash like SHA-256, which GPUs crack at billions per second.”
- “The classic bug is IDOR — authenticated but no ownership check, so changing an ID in the URL reads
someone else’s data. I’d authorize every resource access, not just the endpoint.”
- “Authentication at the gateway, fine-grained authorization in the service — only the service knows
that user 42 owns order 993. And services must not blindly trust identity headers, or anyone
reaching them directly can impersonate.”
- “For a collaboration product with per-document sharing, RBAC breaks down — I’d use a
relationship-based model like Zanzibar, since ‘who can access this through any sharing chain’ is a
graph traversal.”
🛠️ 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