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.
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
Balance$2,400
Page tokennot issued
app.post("/api/transfer", handler)With csurfGood
Balance$2,400
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-
Browser → Server
GET /api/csrf-token -
BrowserStore the token in memory
-
Browser → Server
POST /api/transfer -
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| Method | Token check | Forged request |
|---|
checks: POSTapp.use("/api")Good| Method | Token check | Forged request |
|---|
checks: POST PUT PATCH DELETEapp.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
SameSitedefault. - One middleware, one header in fetch.
Cons
csurfis deprecated; usecsrf-csrfinstead.- Useless against XSS: scripts on your origin read it.
- Header-only auth APIs without cookies don't need it.