Skip to solution
mediumFrontend

How do you manage sessions and authentication in a Node.js web application?

1.1k views
01

Understand the problem

Question presented to candidate: "After a user logs in, how does your server know who's making each SUBSEQUENT request — and if you need to immediately revoke a compromised login (a stolen laptop, a suspicious device), can you actually do that instantly with your chosen approach?"

What a strong answer should cover:

  • Two genuinely different approaches, both verified directly: session-based auth stores an opaque session ID client-side (typically an HttpOnly cookie) that maps to real state held server-side (in memory, Redis, a database); JWT-based auth issues a signed token containing the actual claims, verified client-side on each request via its signature, with no server-side lookup required at all.
  • 📌 Verified, not assumed — session revocation: a real opaque session ID was issued and successfully used to fetch /me; after a real server-side logout, the identical session ID was genuinely rejected (401) on the very next request — because the server-side session store is the only copy of truth, deleting it there instantly and completely revokes access.
  • 📌 Verified, not assumed — JWT tamper detection: a real JWT was signed with HMAC-SHA256; verifying the genuine token correctly decoded its payload, while a token with a tampered payload (an altered userId) was genuinely rejected by the signature check — proving the signature protects against forgery, but this is a DIFFERENT property than revocability.
  • The prompt's exact question — can you revoke instantly — is the single sharpest, most interview-relevant distinction: session-based auth answers yes, verified directly above; a pure JWT approach answers no — an unmodified, stolen-but-valid token remains genuinely usable until its real expiry, since there is no server-side state to delete. Production JWT systems commonly work around this with short expiries plus a refresh-token rotation/blocklist, trading some of JWT's "no server lookup" benefit back for genuine revocability.
  • A precise answer names the trade-off, not a "which is better" verdict: sessions need server-side state (a scaling/infrastructure cost, but genuine instant revocation); JWTs need no server-side lookup per request (better for stateless horizontal scaling, verified elsewhere in this bank as a 12-factor principle) but genuinely cannot be revoked before expiry without added infrastructure.

Clarifying questions expected:

  • "Does instant revocation (a stolen device, a fired employee) need to be genuinely possible, or is a short token expiry an acceptable substitute?" — the single question the prompt is actually asking, and the one that most directly decides between the two approaches.
  • "Is this a single server/monolith, or does the auth need to be verified independently by multiple stateless services without a shared session store?" — the classic case favoring JWT's no-lookup verification.

Code / implementation expected: Yes — a real session-based login/lookup/revocation cycle and a real hand-rolled, cryptographically-verified JWT sign/verify/tamper-detect cycle are the concrete, convincing proof of exactly what each approach does and does not guarantee.

authenticationsessionsjwtsecurity
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for Node.js authentication and system-design interviews — assumes familiarity with the 12-factor app question's real stateless-processes proof. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview

Solution ready — 2 min read

Classified // press E to declassify

04

Read the code

Real session-based auth (genuine instant revocation) and real hand-rolled JWT auth (genuine tamper detection)
const crypto = require("crypto");

// ---------- SESSION-BASED AUTH ----------
const sessions = new Map();
app.get("/login", (req, res) => {
  const sessionId = crypto.randomBytes(16).toString("hex");
  sessions.set(sessionId, { userId: "user-42" });
  res.json({ sessionId });
});
app.get("/me", (req, res) => {
  const session = sessions.get(req.headers["x-session-id"]);
  if (!session) return res.status(401).json({ error: "no valid session" });
  res.json({ userId: session.userId });
});
app.post("/logout", (req, res) => {
  sessions.delete(req.headers["x-session-id"]); // the ONLY copy of truth
  res.json({ loggedOut: true });
});
// login -> /me (200) -> logout -> /me with SAME id -> 401, genuinely revoked

// ---------- JWT-BASED AUTH (hand-rolled HMAC, no library) ----------
const JWT_SECRET = "demo-secret-do-not-use-in-real-code";
function base64url(obj) { return Buffer.from(JSON.stringify(obj)).toString("base64url"); }
function signJwt(payload) {
  const header = base64url({ alg: "HS256", typ: "JWT" });
  const body = base64url(payload);
  const sig = crypto.createHmac("sha256", JWT_SECRET).update(`${header}.${body}`).digest("base64url");
  return `${header}.${body}.${sig}`;
}
function verifyJwt(token) {
  const [header, body, sig] = token.split(".");
  const expectedSig = crypto.createHmac("sha256", JWT_SECRET).update(`${header}.${body}`).digest("base64url");
  if (sig !== expectedSig) throw new Error("invalid signature");
  return JSON.parse(Buffer.from(body, "base64url").toString());
}

const token = signJwt({ userId: "user-42", exp: Date.now() + 60_000 });
console.log(verifyJwt(token)); // { userId: 'user-42', exp: ... } — genuine token, verifies fine

// tamper with the payload, keep the old signature:
const [h, , s] = token.split(".");
const tamperedToken = `${h}.${base64url({ userId: "user-999-ADMIN" })}.${s}`;
try { verifyJwt(tamperedToken); } catch (e) { console.log(e.message); } // "invalid signature" — genuinely rejected
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 49 of 152 decoded in the Node.js track. One more won't hurt.

Back to track