Skip to the solution
MediumBackend8 min read

What is CSRF and how do you protect a Node.js API against it?

1.2k views

The problem

Question presented to candidate: "A user is logged into your site with a session cookie. They visit an unrelated, malicious page in another tab. Can that page make your API do something on their behalf?"

What a strong answer should cover:

  • πŸ“Œ CSRF (Cross-Site Request Forgery) exploits one specific browser behaviour: a browser automatically attaches cookies to a request to a site, regardless of which page or origin triggered that request. The attacker's page never reads the victim's cookie β€” it just gets the browser to send a request that carries it.
  • CSRF is a different problem from CORS. CORS controls whether a script running on one origin can read the response of a cross-origin request. CSRF does not care about reading a response at all β€” a state-changing side effect (a transfer, a password change) happens whether or not the attacker's page can see what came back.
  • The modern, primary defense is the SameSite cookie attribute β€” Strict, Lax, or None β€” which tells the browser itself when to withhold a cookie on a cross-site request, rather than relying on server-side logic to catch a forged request after the fact.
  • The precise, easy-to-get-wrong nuance: SameSite=Lax (the modern browser default when unset) still allows a cookie through on a cross-site top-level GET navigation β€” e.g. a link the victim clicks. It blocks cross-site fetch/XHR/iframe requests, but a plain link click is treated differently. This is exactly why a state-changing action must never be reachable via a GET endpoint.
  • SameSite=None cookies are sent on every request regardless of origin, and modern browsers additionally require the Secure attribute alongside None or the cookie is rejected outright.
  • A CSRF token (double-submit cookie, or a token embedded in the page and echoed back in a custom header) remains a valid defense-in-depth layer, especially for cookies that must be SameSite=None (e.g. a cookie legitimately used cross-site, such as in an embedded widget) β€” the token is something the attacker's page cannot read cross-origin even though the cookie itself is sent.
  • The csurf npm package β€” long the default answer here β€” was deprecated and archived in 2022; a current answer should not name it as the recommended library.

Clarifying questions expected:

  • "Is the vulnerable endpoint reachable via GET, or only via POST/PUT/DELETE?" β€” a GET endpoint with side effects defeats the Lax-blocks-cross-site-writes assumption entirely.
  • "Does any cookie in this app need to be SameSite=None for a legitimate cross-site use case?" β€” that is exactly where a token-based layer earns its place on top of SameSite.

Code / implementation expected: Optional, but showing the double-submit-cookie check and, ideally, the SameSite behaviour itself is the most convincing thing to demonstrate.

  • csrf
  • cors
  • security
  • cookies
  • same-site

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 Β· 9 sections

Read the code

JavaScriptDouble-submit-cookie CSRF check β€” matching, missing, and wrong-token requests, actually run
import express from "express";
import crypto from "node:crypto";

const app = express();
app.use(express.json());

app.get("/csrf-token", (req, res) => {
  const token = crypto.randomBytes(16).toString("hex");
  res.cookie("csrf-token", token, { sameSite: "strict", httpOnly: false });
  res.json({ token });
});

app.post("/transfer", (req, res) => {
  const cookieToken = (req.headers.cookie || "").match(/csrf-token=([^;]+)/)?.[1];
  const headerToken = req.headers["x-csrf-token"];
  if (!cookieToken || !headerToken || cookieToken !== headerToken) {
    return res.status(403).json({ error: "CSRF token mismatch" });
  }
  res.json({ ok: true, amount: req.body.amount });
});

// Set-Cookie header observed: "csrf-token=bc4c...; Path=/; SameSite=Strict"
// matching token         -> 200 { ok: true, amount: 100 }
// missing header token   -> 403 { error: 'CSRF token mismatch' }
// wrong header token     -> 403 { error: 'CSRF token mismatch' }

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 41 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 are 'Event Emitting' patterns and how does error-first callback convention relate to them?

Continue