Implementing CSRF Protection

1. Understanding CSRF Attacks

AspectDetail
AttackAttacker tricks browser into sending authenticated request
RequiresCookie-based session + state-changing endpoint
VectorImage, form auto-submit, fetch from attacker page

2. Implementing CSRF Tokens

Example: Synchronizer token

// Issue: tie to session, embed in form
app.get("/form", (req, res) => {
  req.session.csrf = crypto.randomBytes(32).toString("hex");
  res.render("form", { csrf: req.session.csrf });
});

// Verify
app.post("/action", (req, res) => {
  if (req.body._csrf !== req.session.csrf) return res.status(403).end();
  // ...
});
StepDetail
Set cookieRandom token in cookie (not HttpOnly)
Send headerJS reads cookie, sends in X-CSRF-Token
Server comparesCookie value == header value
BetterHMAC signed cookie (avoids cookie tossing)

4. Implementing Token Validation

PracticeDetail
Constant-time comparePrevent timing leak
Per-sessionBind token to session ID
Per-request (optional)Rotate after each state change
ValueBehavior
StrictNever sent on cross-site (best CSRF defense)
LaxSent on top-level navigation GET (default in modern browsers)
NoneSent always; requires Secure

6. Implementing Custom Headers for CSRF Protection

Browsers block setting custom headers cross-origin without CORS preflight. Requiring X-Requested-With: XMLHttpRequest blocks simple-request CSRF.

7. Using Origin and Referer Headers

Example: Origin check

function checkOrigin(req, res, next) {
  const o = req.headers.origin || req.headers.referer;
  if (!o || !o.startsWith("https://app.example.com")) return res.status(403).end();
  next();
}

8. Implementing CSRF Middleware

LibraryDetail
Expresscsurf (deprecated) → csrf-csrf, lusca
DjangoCsrfViewMiddleware (built-in)
SpringCsrfFilter (default on)
Railsprotect_from_forgery

9. Handling Token Rotation

WhenDetail
After loginRotate session + CSRF token
After privilege changeNew token
Per-request (high security)Single-use tokens

10. Implementing Exempting Safe Methods

MethodCSRF Check
GET / HEAD / OPTIONSNo (must be side-effect free)
POST / PUT / DELETE / PATCHRequired
Anti-patternState change on GET (e.g., /delete?id=1)