
Authentication & Authorization
Sessions vs JWTs, OAuth 2.0 and OIDC without the hand-waving, MFA in practice, and the difference between RBAC and ABAC. The two halves engineers most often confuse — and what to do about it.
What you will learn
Authentication and authorization are easy to say in one breath. Treating them as one thing is the most common security bug on the internet. Both OWASP A01 Broken Access Control and A07 Identification & Authentication Failures exist because someone wrote a check that conflated logged in with allowed.
Authentication — Proving Identity
Authentication asks who are you? and demands evidence. Evidence comes in factors:
- Something you know — password, PIN.
- Something you have — phone, hardware key (YubiKey), trusted device.
- Something you are — fingerprint, face, voice.
True multi-factor authentication mixes different factors — password + hardware key, not password + security question. SMS one-time codes count as "something you have," but are weak: SIM-swap attacks have stolen tens of millions of dollars in the past five years. NIST 800-63B has effectively deprecated SMS for high-assurance use.
| Method | Phishing-resistant? | Strength | Notes |
|---|---|---|---|
| Password alone | ❌ | Weak | Reuse + breach lists make most accounts vulnerable |
| Password + SMS OTP | ❌ | Better | Vulnerable to SIM swap, real-time phishing |
| Password + TOTP (authenticator app) | ❌ (real-time phishable) | Good | Standard for most apps |
| Password + WebAuthn / passkey | ✅ | Strong | Origin-bound; phishing-proof |
| Passkey only (passwordless) | ✅ | Strong | WebAuthn level 3; the modern target |
Sessions — The Workhorse
The simplest scheme: after login, the server issues a random opaque token, sets a cookie, and stores the session record. Every request, the cookie comes back; the server looks it up. Logging out invalidates the session row — instantly.
Cookie attributes are non-negotiable in 2025:
Set-Cookie: sid=...; Secure; HttpOnly; SameSite=Lax;
Path=/; Max-Age=86400- Secure — only over TLS.
- HttpOnly — invisible to JavaScript (mitigates XSS theft).
- SameSite=Lax (or Strict) — blocks cross-site requests from carrying it (CSRF defense).
- __Host- prefix for sensitive cookies — forces Secure + Path=/ + no Domain.
JWTs — Useful, Routinely Misused
A JWT is three base64 parts: header, payload, signature. The signature is over the first two with either HMAC (HS256, shared secret) or a public-key signature (RS256, ES256, EdDSA). The payload is readable — JWTs encode, they do not encrypt.
eyJhbGciOiJFUzI1NiIsImtpZCI6IjE0In0.eyJzdWIiOiJ1XzQyIiwic2NvcGUiOiJyZWFkOm9yZGVycyIsImV4cCI6MTcwOTI4MDAwMH0.MEUCIQDk… # header payload signature
alg header — historic libraries accepted alg: none or downgraded RS256 to HS256 using the public key as a shared secret. Always pin the algorithm server-side. 2) No revocation — until the token expires, it works. Keep access-token TTL short (5–15 min) and use refresh tokens. 3) Storing in localStorage — readable by any XSS payload. Prefer HttpOnly cookies. 4) Algorithm confusion — verify with the public key, never the public key as if it were a secret.When to Pick Which
- Single-domain web app
- You need instant logout / revocation
- You're fine with a session store (Redis, Postgres)
- You want the smallest attack surface
- Service-to-service across orgs / clouds
- Mobile or third-party API access
- Stateless verification matters (CDN, edge)
- You can keep TTLs short and use a refresh-token rotation
OAuth 2.0 — Delegated Access
OAuth answers a different question: how does an app act on a user's behalf at another service, without handling the user's password? Authorization Code with PKCE is the only flow you should reach for in 2025 (yes, even for SPAs).
OIDC — Authentication on Top of OAuth
OAuth 2.0 grants an access token; it doesn't tell you who the user is. OpenID Connect (OIDC) layers on top: the token endpoint also returns an id_token — a signed JWT with the user's identity (sub, email, name). Always validate iss, aud, exp, and the signature against the IdP's JWKS.
Authorization — The Hard Half
Now the user is identified. What may they actually do? Two dominant models, plus a third for cross-cutting policy.
RBAC — Role-Based
Users get one or more roles; roles are bundles of permissions. Simple to reason about. Right answer for most apps.
# roles → permissions role viewer → orders:read role manager → orders:read, orders:refund role admin → orders:*, users:*
ABAC — Attribute-Based
Permissions are computed from attributes of the user, the resource, and the context. Necessary when policy depends on data values, not just on who you are.
allow if user.dept == order.dept and order.amount < 50000 and request.time.hour < 22
ReBAC — Relationship-Based
Permissions follow graph edges ("X is in folder Y, which is shared with team Z"). Google's Zanzibar paper popularised it; engines like SpiceDB, OpenFGA, and AuthZed implement it. Right answer for collaborative apps (Drive, GitHub, Notion).
/api/orders/{id}. Auth middleware verifies the JWT and lets the request through. The handler queries SELECT * FROM orders WHERE id = $1. Why is this broken?Show answer
$1 belongs to the caller (AuthZ). Every authenticated user can read every order — IDOR. Fix: WHERE id = $1 AND tenant_id = $2 AND owner_id = $3, with all three parameters derived from the verified JWT, never from the request body.The Most Common AuthN/AuthZ Mistakes
- No object-level authorization. The cause of nearly every IDOR.
- Trusting client-side checks. "Hide the admin button if not admin" — useful UX, useless security. Always re-check on the server.
- Mass assignment. Updating
UserUpdate{...}withreq.bodydirectly. Attacker adds"role": "admin"in the JSON. Use allow-lists. - JWTs without revocation. A leaked token is valid until expiry. Keep TTLs short; track a denylist for forced logout.
- Open redirect on callback. Allowing arbitrary
redirect_urivalues turns OAuth into a phishing primitive. Strict allow-list of registered URIs.
code_verifier). It defeats authorization-code interception on mobile/SPAs (and on confidential clients via redirect-URI bugs). OAuth 2.1 makes PKCE mandatory for all flows. Always implement it; it's free.- Who — verify the token (signature, exp, iss, aud)
- What — the action's required role/scope
- Which — the specific resource: tenant, owner, status
- When — context: IP, time, MFA-fresh-within-N-min
alg must be pinned server-side. 4) OAuth 2.0 + PKCE + OIDC is the default for delegated identity. 5) Every protected request must answer Who, What, Which, When — never assume the previous middleware did your authorization for you.- NIST SP 800-63B — Digital Identity Guidelines (authentication)nist.gov
- OAuth 2.1 — current consolidated draftdatatracker.ietf.org
- RFC 8725 — JSON Web Token Best Current Practicesdatatracker.ietf.org
- WebAuthn Level 3 — phishing-resistant authenticationw3.org
- OWASP Authentication Cheat Sheetowasp.org
- OWASP Session Management Cheat Sheetowasp.org
- Google Zanzibar paper — relationship-based authorization at scaleresearch.google
Finished reading?