> cs·fundamentals
interview 0% 24m read
2.3 ★ core [J][A][I] 14 interview Q's

Auth & security concepts

Authentication vs authorization, sessions+cookies vs JWT, and OAuth2 vs OIDC — distinct jobs that interviewers love to conflate.

Four terms get conflated constantly — authentication, authorization, OAuth2, and OIDC — and interviewers love to watch you untangle them. They answer different questions: who are you, what may you do, can app A act for me on B, and who logged in. Keep those four questions straight and the whole area stops being a fog of acronyms.

Authentication vs authorization

Authentication runs first and establishes identity; authorization runs second and gates actions against that identity. A 401 means authentication failed (no/invalid credentials — retry with auth); a 403 means authentication succeeded but authorization denied the action (you’re known, just not allowed). The number one tell of a junior answer is swapping these two.

Keep them in separate layers. The middleware that authenticates a request (verify the token, attach req.user) should not also decide what that user may touch — that’s the job of an authorization check at the resource, where the policy lives (“does this user own order 42?”). Mixing them produces the classic bug where a valid login leaks access to other users’ data because nobody checked ownership.

Sessions vs JWT

ApproachUse whenAvoid whenGotcha
Sessions + cookiesserver-rendered apps, you need instant revoke / logout-everywherestateless multi-service APIs where a shared session store is a bottleneckneeds a session store; CSRF risk — set SameSite + CSRF token
JWT (stateless)APIs / microservices verifying a token without a central lookupyou need to revoke immediately or store much per-user statecan't easily revoke before expiry — keep TTL short + use refresh tokens
Sessions trade a lookup for easy revocation; JWTs trade revocation for statelessness.

The fundamental trade is where the truth lives. A session keeps the truth on the server — the cookie is just a meaningless random ID, and deleting the server row instantly logs the user out everywhere. A JWT moves the truth into the token — the server can verify it with just a public key and no database call, which is gold for microservices, but there’s nothing to delete, so the token stays valid until it expires.

Session vs JWT verification
SESSION (stateful)
  Login → server creates session, stores it, sets cookie:
          sid=opaqueRandom; HttpOnly; Secure; SameSite=Lax
  Each request → server looks up sid in the store → identity.
  Logout → delete the row. Instant, everywhere.

JWT (stateless)
  Login → server signs a token:
          header.payload.signature
          payload = { sub: 42, role: "admin", exp: 1700000000 }
  Each request → Authorization: Bearer <jwt>
          server verifies the signature with its key — NO DB lookup.
  Logout → ??? the token is valid until exp.
          Mitigate: short TTL + refresh token (or a revocation list).

The JWT carries its claims inside it, so any service holding the public key can validate it independently — that’s the appeal for microservices, and also exactly why you can’t simply “delete” it to log someone out. The standard answer to revocation is a short-lived access token (minutes) paired with a refresh token: revoke the refresh token, and the access token dies on its own within minutes.

OAuth2 vs OIDC

OAuth2 was built to delegate authorization: you let a third-party app obtain a scoped access token to call an API on your behalf, without handing over your password. “Let this app post to my calendar” is OAuth2 — the app gets a token good for calendar.write and nothing else. OIDC (OpenID Connect) layers authentication on top — it reuses the same OAuth2 flow but also returns an id_token (a JWT describing who logged in). “Login with Google” is OIDC.

The one-line distinction interviewers want: OAuth2 answers what may this app do for me; OIDC answers who is this user. OAuth2 alone was never designed to prove identity — apps that misused its access token as a login signal created real security holes, which is precisely the gap OIDC’s id_token was created to fill.

Four columns — User, App, Authorization Server, Resource API — with arrows: user clicks login, app redirects to the auth server, user authenticates, auth server returns a code, app exchanges the code for an id_token and access_token, app calls the resource API with the access token.UserYour AppAuth ServerResource API1. click login2. redirect (scopes)3. user authenticates + consents4. one-time code5. exchange code (server-side)6. id_token + access_token7. call API with access_token
FIG 1 · OAuth2 / OIDC authorization-code flow The user authenticates at the provider; the app receives a one-time code, then exchanges it server-side for tokens. The id_token (OIDC) says who; the access_token (OAuth2) says what.

The flow above is the authorization-code grant — the secure default for web apps. The key design choice is that the app never sees the user’s password and receives a short-lived code in the browser redirect, then swaps that code for tokens in a back-channel server-to-server call (step 5). That swap requires the app’s client secret, so even if an attacker intercepts the code in the URL they can’t redeem it. (Public clients like SPAs and mobile apps add PKCE to plug the same hole without a secret.)

01 Learning objectives

0 / 4 done

02 Curated reading

03 Knowledge check

knowledge check3 questions · pass ≥ 70%
  1. 01easy

    Authentication answers which question?

  2. 02medium

    A key downside of stateless JWTs versus server sessions is:

  3. 03medium

    “Login with Google” on top of OAuth2 is provided by:

04 Interview questions

browse all ↗

What gets asked on this topic — tap a card for how to approach it, the follow-ups, and the trap. Company tags are best-effort & sourced.

  • Commonly asked junior concept very common Authentication vs authorization — state the difference crisply with an example.

    Authentication answers 'who are you?' — verifying identity (password, token, passkey). Authorization answers 'what are you allowed to do?' — checking permissions after identity is established.

    Example: logging in with your password is authentication; the check that decides you can read but not delete the document is authorization. Authn always precedes authz. The corresponding status codes: 401 Unauthorized = not authenticated; 403 Forbidden = authenticated but not permitted.

    Red flag Swapping 401 and 403, or saying 'authorization checks your password'. Authorization assumes identity is already known.

    source: Auth0 — Authentication vs Authorization ↗
  • Commonly asked mid concept very common Session cookies vs JWTs for API auth — compare the tradeoffs. How do you revoke each?

    Sessions: server stores session state, the client holds an opaque session id in an HttpOnly cookie. Stateful, but revocation is trivial — delete the server-side session. Needs shared session storage to scale horizontally.

    JWTs: a signed, self-contained token the server verifies without a lookup — stateless and scales easily. The catch is revocation: a valid JWT is honored until it expires, so logout/ban requires a denylist or short expiry + refresh tokens, which reintroduces state. Use short-lived access tokens (minutes) plus a refresh token to limit the blast radius.

    Red flag Calling stateless JWTs strictly better. Their headline weakness is revocation; any real logout/ban story drags state back in.

    source: Auth0 — Token-based vs session-based authentication ↗
  • Commonly asked mid concept very common OAuth2 vs OIDC — what is each actually for? Don't conflate them.

    OAuth 2.0 is delegated authorization: 'let app A access my data on service B' without sharing my password — it issues access tokens scoped to resources. It says nothing about who the user is.

    OIDC (OpenID Connect) is an authentication layer built on top of OAuth2. It adds an ID token (a JWT) and a standard /userinfo endpoint, so the app learns *who* logged in — this is what powers 'Log in with Google'. So: OAuth2 = access to resources; OIDC = proof of identity.

    Red flag Using a bare OAuth2 access token to authenticate a user. Access tokens are for resource access; identity comes from the OIDC ID token.

    source: OpenID Connect — How it works ↗
  • Commonly asked senior design common Walk through the OAuth2 authorization code flow. Why was PKCE added?

    Authorization code flow: the app redirects the user to the auth server; the user authenticates and consents; the auth server redirects back with a short-lived authorization code; the app's backend exchanges that code (plus its client secret) for an access token over a back channel. Keeping tokens off the front channel is the point.

    PKCE (Proof Key for Code Exchange) hardens this for public clients (SPAs, mobile) that can't keep a secret. The client sends a hashed code_challenge up front and the original code_verifier at exchange time, so a stolen authorization code is useless without the verifier. PKCE is now recommended for all clients.

    Red flag Using the deprecated implicit flow (tokens in the URL fragment) for SPAs. The modern guidance is auth-code + PKCE.

    source: oauth.com — Authorization Code with PKCE ↗
  • Commonly asked senior concept common Where should a browser store an access token, and how do the choices map to XSS vs CSRF?

    localStorage is readable by any JavaScript on the page, so a single XSS flaw leaks the token. An HttpOnly cookie is invisible to JS (XSS can't read it) but is sent automatically, which opens CSRF.

    The pragmatic answer: store tokens in HttpOnly, Secure, SameSite=Lax/Strict cookies and add anti-CSRF defenses (SameSite already blocks most cross-site sends; add a CSRF token for the rest). Keep access tokens short-lived. There's no storage location immune to a compromised front end — defense in depth plus a tight CSP matters more than the slot.

    Red flag Claiming HttpOnly cookies are 'XSS-proof and safe'. They stop token theft via JS but are auto-sent, so you still need CSRF protection.

    source: OWASP — JWT / token storage cheat sheet ↗
  • Commonly asked mid concept common How should passwords be stored, and why is a fast hash like SHA-256 the wrong choice?

    Never store plaintext or reversible encryption. Use a slow, salted, adaptive password hash — bcrypt, scrypt, or Argon2 (the current OWASP-preferred). The salt (unique per user) defeats rainbow tables; the deliberate slowness/work factor caps how many guesses an attacker can make per second after a breach.

    Fast general-purpose hashes (SHA-256, MD5) are wrong precisely because they're fast — a GPU computes billions per second, making offline brute force cheap. Choose a memory-hard function and raise the cost factor as hardware improves.

    Red flag Using SHA-256/MD5 (even salted) for passwords. They're built to be fast, which is the opposite of what password hashing needs.

    source: OWASP — Password Storage Cheat Sheet ↗
  • Commonly asked mid concept common Why use short-lived access tokens with refresh tokens instead of one long-lived token?

    A stateless access token can't be revoked before it expires, so you want it to live only minutes — that bounds the damage if it leaks. To avoid forcing the user to log in every few minutes, a longer-lived refresh token (stored more securely, server-trackable) is exchanged for fresh access tokens.

    This splits concerns: access tokens are stateless and fast to verify; refresh tokens are the revocable, stateful part. Add refresh token rotation (issue a new refresh token each use and invalidate the old one) so a stolen refresh token is detected on reuse.

    Red flag Issuing a long-lived access token 'for convenience'. If it leaks you have no way to revoke it until expiry.

    source: Auth0 — Refresh tokens ↗
  • Commonly asked senior concept occasional Common pattern: use OAuth/OIDC to log in, then issue your own session or JWT. Why do that instead of using the provider's token directly?

    After OIDC verifies identity, you typically mint your own session/JWT rather than passing Google's token around. Reasons: you control expiry and revocation; you attach your app's roles/permissions and user id; you don't couple every internal service to the external provider's token format or availability; and you avoid leaking a powerful provider token across your backend.

    The provider token is used once at login to establish identity; from then on your own credential governs the session.

    Red flag Forwarding the raw Google/Apple token to every internal service. It couples you to the provider and complicates revocation and authorization.

    source: OAuth.com — OAuth 2.0 Simplified ↗
  • ★ must-know Commonly asked mid concept very common What is CSRF, and why does a CSRF attack work even though the attacker never sees the victim's cookie?

    CSRF (Cross-Site Request Forgery) tricks a logged-in victim's browser into making a state-changing request to your site. The attacker hosts a page that auto-submits a form (or fires a request) to yourbank.com/transfer; because the browser automatically attaches the victim's cookies to any request to that origin, the request arrives authenticated — even though the attacker never read the cookie.

    The core enabler is ambient authority: cookies ride along by default. Defenses: SameSite cookies (block cross-site sends), anti-CSRF tokens (a secret the attacker's page can't know), and checking Origin/Referer.

    What a strong answer covers
    • The browser auto-sends cookies to the target origin — the attacker exploits that, not the cookie value.

    • Only state-changing requests matter; CSRF can't read the response (same-origin policy).

    • SameSite=Lax/Strict cookies are the first-line modern defense.

    • Anti-CSRF tokens add a secret the attacker's page cannot supply.

    Quick self-check

    Why does a CSRF attack succeed without the attacker ever reading the session cookie?

    Red flag Thinking HTTPS or HttpOnly stops CSRF. They don't — the browser still auto-attaches the cookie. SameSite and CSRF tokens are the defenses.

    source: OWASP — Cross-Site Request Forgery Prevention Cheat Sheet ↗
  • Commonly asked mid concept very common Walk through the three parts of a JWT. What does the signature guarantee — and what does it NOT?

    A JWT is header.payload.signature, each base64url-encoded and joined by dots. The header names the algorithm; the payload holds the claims (sub, exp, roles); the signature is computed over header+payload with a secret (HMAC) or private key (RSA/ECDSA).

    The signature guarantees integrity and authenticity — the server detects any tampering and confirms the token was issued by a holder of the key. It does not provide confidentiality: the payload is merely encoded, not encrypted, so anyone can base64-decode and read it. Never put secrets in a JWT payload, and always verify the signature server-side.

    What a strong answer covers
    • Three parts: header, payload (claims), signature — base64url, dot-separated.

    • Signature → integrity + authenticity (tamper-evident, proves the issuer).

    • Payload is encoded, not encrypted — readable by anyone; no secrets in it.

    • Standard claims: sub, exp, iat, iss, aud.

    Quick self-check

    What does a valid JWT signature prove?

    Red flag Storing sensitive data in the JWT payload assuming it's hidden. It's base64-decodable plaintext — signing protects integrity, not confidentiality.

    source: jwt.io — Introduction to JSON Web Tokens ↗
  • Commonly asked senior debug occasional Debugging: a JWT library accepts a token with alg: none and lets a forged admin token through. What happened?

    This is the classic alg: none / algorithm-confusion vulnerability. The JWT header declares its own algorithm; if the verifier trusts that field, an attacker sets alg: none (or strips the signature) and the library skips verification, accepting a payload they forged (role: admin). A related attack swaps RS256 for HS256, signing with the public key as if it were an HMAC secret.

    Fix: never let the token dictate the algorithm. Configure the verifier with an allowlist of expected algorithms, reject none, and validate exp/aud/iss. Treat the header's alg as untrusted input.

    What a strong answer covers
    • The bug: the verifier trusts the attacker-controlled alg header.

    • alg: none tells naive libraries to skip signature verification entirely.

    • RS256→HS256 confusion lets the public key be abused as an HMAC secret.

    • Fix: pin the expected algorithm(s) server-side; reject none; verify standard claims.

    Quick self-check

    What's the root cause of the alg:none JWT bypass?

    Red flag Calling a generic `verify()` that honors the token's own `alg`. Always pass an explicit algorithm allowlist; never accept `none`.

    source: Auth0 — Critical vulnerabilities in JSON Web Token libraries ↗
  • Commonly asked senior concept common RBAC vs ABAC — what's the difference, and when do you outgrow roles?

    RBAC (Role-Based Access Control) grants permissions through roles: a user is an editor, the editor role can update:article. Simple, auditable, and enough for most apps. It strains when access depends on context beyond a role — ownership, department, time of day, resource attributes — leading to a 'role explosion' (editor_team_a_readonly_weekends).

    ABAC (Attribute-Based Access Control) decides via policies over attributes of the user, resource, action, and environment (e.g. 'allow if user.dept == resource.dept and time is business hours'). It's far more expressive but harder to reason about and audit. Start with RBAC; reach for ABAC when contextual, fine-grained rules cause role explosion.

    What a strong answer covers
    • RBAC: permissions via roles — simple, auditable, sufficient for most apps.

    • ABAC: policies over user/resource/action/environment attributes — expressive, context-aware.

    • Role explosion signals you've outgrown pure RBAC.

    • ABAC trades simplicity/auditability for fine-grained flexibility.

    Quick self-check

    Requirement: 'a user may edit a document only if they are in the same department as the document.' Which model fits naturally?

    Red flag Encoding contextual rules as ever-more-specific roles. When permissions depend on resource attributes or context, that's an ABAC need, not more roles.

    source: Auth0 — RBAC vs ABAC ↗
  • Commonly asked senior concept occasional Why compare password hashes (and tokens) with a constant-time comparison instead of ==?

    A normal string == short-circuits at the first mismatching byte, so it returns faster the earlier the difference. An attacker measuring response timing can exploit this timing side channel to recover a secret (an API token or HMAC) byte by byte — try values until the comparison takes slightly longer, meaning one more byte matched.

    A constant-time comparison always examines the full length regardless of where bytes differ, leaking no timing information. Use the platform's crypto.timingSafeEqual / hmac.compare_digest for tokens, HMAC tags, and similar secrets. (Note: bcrypt/Argon2 verification already handles this for passwords.)

    What a strong answer covers
    • == short-circuits, so its runtime depends on how many leading bytes match.

    • An attacker can recover a secret byte-by-byte from timing differences.

    • Constant-time compare scans the full input regardless of mismatches.

    • Use crypto.timingSafeEqual / hmac.compare_digest for token/HMAC checks.

    Red flag Comparing secret tokens or HMAC signatures with ordinary string equality. The early-exit timing leak can let an attacker brute-force the secret one byte at a time.

    source: OWASP — Cryptographic Storage Cheat Sheet ↗
  • Commonly asked senior concept occasional What is a pepper, and how does it differ from a salt in password hashing?

    A salt is a unique random value stored *alongside* each password hash; it ensures identical passwords produce different hashes and defeats precomputed rainbow tables. It's not secret — it lives in the database with the hash.

    A pepper is a single secret value mixed into every password before hashing, but kept outside the database (in app config, a secret manager, or an HSM). The point of defense-in-depth: if an attacker steals only the database, the salts don't help them, and without the pepper they still can't crack the hashes offline. Salt = per-user, public, in DB; pepper = global, secret, outside DB.

    What a strong answer covers
    • Salt: per-user, random, stored with the hash — kills rainbow tables.

    • Pepper: global secret, kept out of the DB — defends against DB-only theft.

    • They're complementary, not alternatives.

    • Pepper rotation is harder, so it's stored in config/secret manager/HSM.

    Quick self-check

    What distinguishes a pepper from a salt?

    Red flag Storing the pepper in the same database as the hashes — that defeats its entire purpose. The pepper's value comes from living somewhere a DB dump won't expose.

    source: OWASP — Password Storage Cheat Sheet (Peppering) ↗