Skip to the solution
MediumFrontend8 min read

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

1.1k views

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.

  • authentication
  • sessions
  • jwt
  • security

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

8 min read Β· 8 sections

Read the code

JavaScriptReal 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

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 49 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextMedium

What is the difference between spawn() and exec() in Node.js?

Continue