Require CSRF tokens

Require a token header on every POST, PUT and DELETE.

From security-best-practices: Step 3, Prevent CSRF.

Another site moves your money

Claim the prize, then paste the page token and claim again.

https://evil.example/prize

You won a $500 gift card

Hidden form posts to both banks.

<form method="POST"
  action="https://bank.example/api/transfer">
  <input name="to" value="mallory">
  <input name="amount" value="500">
  <input name="_csrf" value="">
</form>
No token checkBad

Page tokennot issued

    app.post("/api/transfer", handler)
    With csurfGood

    Page token

      app.post("/api/transfer", csrfProtection, handler)
      const { csrfToken } = await (await fetch("/api/csrf-token")).json();
      
      await fetch("/api/transfer", {
        method: "POST",
        headers: { "Content-Type": "application/json", "CSRF-Token": csrfToken },
        body: JSON.stringify({ to: "alice", amount: 50 }),
      });

      Issue, store, send, compare

      Step through. Edit the header or rotate the session.

      csurf, cookie: trueStep 0 of 4
      1. Browser → ServerGET /api/csrf-token

      2. BrowserStore the token in memory

      3. Browser → ServerPOST /api/transfer

      4. ServerRecompute from the cookie secret, compare

      csrf({ cookie: true })
      import csrf from "csurf";
      import cookieParser from "cookie-parser";
      
      app.use(cookieParser());
      const csrfProtection = csrf({ cookie: true });
      
      app.get("/api/csrf-token", csrfProtection, (req, res) => {
        res.json({ csrfToken: req.csrfToken() });
      });
      
      app.use((err, req, res, next) => {
        if (err.code !== "EBADCSRFTOKEN") return next(err);
        res.status(403).json({ error: "invalid csrf token" });
      });

      Which methods get checked

      Pick a method and compare both tables.

      app.post("/api/*")Bad
      Results with csrfProtection on POST routes only
      MethodToken checkForged request
      checks: POST
      app.use("/api")Good
      Results with csrfProtection on every API method
      MethodToken checkForged request
      checks: POST PUT PATCH DELETE
      app.use("/api", csrfProtection);
      
      app.get("/api/transfer", ...);   // wrong: an <img> can trigger it
      app.post("/api/transfer", ...);  // right

      Why it matters, pros and cons

      Why it matters

      The browser sends the cookie from any page. Only your origin can read the token.

      Pros

      • Forged forms, images and links get a 403.
      • Works regardless of the browser's SameSite default.
      • One middleware, one header in fetch.

      Cons

      • csurf is deprecated; use csrf-csrf instead.
      • Useless against XSS: scripts on your origin read it.
      • Header-only auth APIs without cookies don't need it.