TL;DR: CSRF attacks trick authenticated users into making unintended requests by exploiting browser cookie behavior. Defenses: SameSite cookie attributes (simplest — most modern browsers support it), CSRF tokens (for forms), and verifying the Origin or Referer header. If you use cookie-based auth, always set SameSite=Lax or Strict and Secure=true on your session cookies.

How CSRF Works

Imagine you are logged in to your bank at bank.com. Your browser has a session cookie for the bank. Now you visit evil.com, which has hidden HTML like this:

<!-- On evil.com -->
<img src="https://bank.com/transfer?to=attacker&amount=1000" />
<!-- Or a hidden auto-submitting form -->
<form action="https://bank.com/transfer" method="POST" id="csrf">
  <input type="hidden" name="to" value="attacker" />
  <input type="hidden" name="amount" value="1000" />
</form>
<script>document.getElementById('csrf').submit()</script>

Your browser automatically sends your bank.com session cookie with both requests. The bank's server sees an authenticated transfer request — from your browser, with your valid session. Without CSRF protection, it processes the transfer.

The attacker never needed your password. They just needed you to visit their page while you had an active session on the target site.

CSRF Defenses

1. SameSite Cookie Attribute (simplest, most modern)

// Express — setting a secure, SameSite cookie
res.cookie('session', token, {
  httpOnly: true,       // JavaScript cannot read it
  secure: true,         // HTTPS only
  sameSite: 'Lax',      // Not sent with cross-site POST requests
  maxAge: 7 * 24 * 60 * 60 * 1000  // 7 days
})

SameSite=Lax (the browser default in modern browsers) prevents the cookie from being sent with cross-site POST requests — which blocks most CSRF attacks. SameSite=Strict is more restrictive and also blocks the cookie from cross-site GET requests (e.g., clicking a link from another site).

2. CSRF Tokens

The server generates a unique random token and embeds it in every form. On submission, the server checks the token matches. Malicious sites cannot read the token from your page (same-origin policy blocks cross-site reads), so they cannot forge a valid request.

// Express with csrf middleware
import csrf from 'csrf'
const tokens = new csrf()

// Generate token and put in form
const secret = await tokens.secret()
req.session.csrfSecret = secret
const token = tokens.create(secret)
// Pass token to template: <input type="hidden" name="_csrf" value="${token}">

// Validate on POST
const valid = tokens.verify(req.session.csrfSecret, req.body._csrf)
if (!valid) return res.status(403).json({ error: 'Invalid CSRF token' })

3. Verify Origin / Referer Header

Check that requests to state-changing endpoints come from your own origin:

function verifySameOrigin(req, res, next) {
  const origin = req.headers.origin || req.headers.referer
  const allowed = ['https://myapp.com', 'https://www.myapp.com']
  if (origin && !allowed.some(a => origin.startsWith(a))) {
    return res.status(403).json({ error: 'Forbidden' })
  }
  next()
}

CSRF in AI-Generated Code

AI commonly generates vulnerable cookie configurations:

// ❌ AI often generates this (missing security attributes)
res.cookie('session', token, { httpOnly: true })

// ✅ What it should be
res.cookie('session', token, {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'Lax',
  maxAge: 7 * 24 * 60 * 60 * 1000
})

When reviewing AI-generated auth code: always search for res.cookie or cookie()` calls and verify all four attributes are present: httpOnly, secure, sameSite, and maxAge/expires.

JWTs and CSRF

JWTs stored in localStorage are immune to CSRF because JavaScript must explicitly include them — a cross-site form submission cannot access localStorage. But localStorage is vulnerable to XSS.

JWTs stored in cookies are vulnerable to CSRF. The solution: use cookie-stored JWTs with SameSite=Strict or Lax, which blocks most cross-site submission attacks.

What to Learn Next

  • What Is XSS? — The complementary attack to CSRF; localStorage vs. cookie storage tradeoff.
  • What Is a JWT? — How token storage choice affects CSRF risk.
  • API Security Guide — Comprehensive security checklist for AI-generated APIs.

Action Item

Search your codebase for res.cookie right now. For every auth-related cookie, confirm it has sameSite: 'Lax', httpOnly: true, and secure: true. This five-minute audit closes the most common CSRF vulnerability in AI-generated Express/Node apps.

FAQ

CSRF (Cross-Site Request Forgery) is an attack where a malicious website tricks a user's browser into making an authenticated request to another site without the user's knowledge. Because browsers automatically send cookies with requests, the server cannot distinguish legitimate from forged requests without CSRF protection.

The most common defense is SameSite cookie attributes — setting SameSite=Lax or Strict on your auth cookies prevents them from being sent with cross-site requests. The synchronizer token pattern (CSRF tokens in forms) is another approach where the server validates a secret token only it and the legitimate page know.

JWTs in localStorage are immune to CSRF (but vulnerable to XSS). JWTs in cookies are still vulnerable to CSRF — use SameSite=Lax or Strict cookie attributes to protect them. There is no perfect solution; the choice is a tradeoff between XSS and CSRF risk.

SameSite controls whether a cookie is sent with cross-site requests. SameSite=Strict: never sent cross-site. SameSite=Lax: sent with top-level navigation (link clicks) but not embedded requests (forms, images). Most modern browsers default new cookies to Lax, providing baseline CSRF protection.

Yes, particularly for apps using cookie-based authentication. Modern defenses have made it less common, but AI-generated code frequently omits proper cookie attributes, creating vulnerability. Always verify your auth cookies include SameSite and Secure attributes.