AuthN & AuthZ
Who are you, and what may you do — passwords done right, sessions vs tokens, OAuth without the hand-waving.
Two Different Questions
Authentication (authn) verifies identity: who are you? Authorization (authz) verifies permission: may you do this? They fail differently — a broken login is authn; reading someone else’s invoice by changing an ID in the URL is authz (and it’s the more common bug). HTTP even splits the status codes: 401 unauthenticated, 403 unauthorized.
Storing Passwords — the Only Right Way
Never store passwords, encrypted or otherwise — store a slow, salted hash. The salt (random, per-user, stored beside the hash) kills rainbow tables and makes identical passwords hash differently. The slowness (bcrypt, scrypt, argon2 — tunable cost) turns a billion-guesses-per-second GPU into thousands.
from argon2 import PasswordHasher ph = PasswordHasher() # sane defaults: memory-hard, salted digest = ph.hash("correct horse battery staple") # store digest; salt & params live inside the string ph.verify(digest, attempt) # raises on mismatch
Sessions vs JWT
- Opaque ID in an httpOnly cookie
- State server-side — instant revocation
- Needs shared session store to scale (Redis)
- Default choice for first-party web apps
- Signed claims live in the token itself
- Stateless — any instance verifies locally
- Revocation before expiry needs a denylist
- Fits APIs, microservices, mobile clients
Cookie flags are non-negotiable either way: httpOnly (JS can’t read it — blunts XSS token theft), Secure (HTTPS only), SameSite=Lax/Strict (blunts CSRF). For JWTs: short expiry + refresh tokens, verify the algorithm server-side (reject alg: none), and never put secrets in the payload — it’s only signed, not encrypted.
OAuth 2.0 & OIDC — Delegated Access
OAuth answers: how does app A get limited access to your data in service B without ever seeing your B password? The authorization-code flow: app redirects you to the provider → you log in and consent there → provider redirects back with a one-time code → app’s backend exchanges code + client secret for an access token, scoped and expiring.
OIDC is a thin identity layer on top: an id_token (JWT) asserting who logged in — OAuth is for access, OIDC is for login. For authorization inside your app, start with RBAC (roles → permissions) and check permissions server-side on every request — never trust the client’s word for its role.
Interview Questions
401 vs 403 — and which layer failed?
401: we don’t know who you are — authentication failed, retry with credentials. 403: we know exactly who you are and the answer is no — authorization denied. Different bugs, different fixes.
Why bcrypt/argon2 over SHA-256 for passwords?
SHA-256 is built for speed — GPUs try billions per second. bcrypt/scrypt/argon2 are deliberately slow and (argon2/scrypt) memory-hard, with tunable cost, collapsing offline cracking rates. Plus built-in per-user salts.
How would you revoke a JWT before it expires?
You can’t un-sign it — so keep expiry short with refresh tokens, and maintain a server-side denylist of revoked token IDs (jti) checked on each request. Note that this reintroduces state — the honest trade-off interviewers want acknowledged.
Walk through the OAuth authorization-code flow — why the code step?
Redirect → consent at provider → code back via browser → backend exchanges code + client secret for tokens. The code exists because the browser redirect is observable: the code alone is useless without the client secret (or PKCE verifier for public clients), so an intercepted redirect leaks nothing usable.