CSRF Explained: Cross-Site Request Forgery in Plain English

TL;DR

CSRF tricks logged-in users into submitting requests they never intended to make. A malicious page can silently POST to your site using the visitor's existing session cookies, and the server can't tell the difference. SameSite cookies stop most of it. Add CSRF tokens for anything sensitive.

What Is CSRF?

Cross-Site Request Forgery (CSRF, pronounced "sea-surf") is an attack where your own browser makes requests you didn't authorize. The site receiving the request can't tell whether you clicked a button on their page or whether a third-party page triggered it behind the scenes.

A concrete example:

  1. You're logged into your bank
  2. You open a separate tab and visit a site that's been compromised
  3. That page silently submits a money transfer form to your bank
  4. Your browser includes your banking cookies automatically
  5. The transfer goes through

How CSRF Attacks Work

Basic Form Attack

Malicious page that transfers money
<!-- On evil-site.com -->
<html>
<body onload="document.forms[0].submit()">
  <form action="https://yourbank.com/transfer" method="POST">
    <input type="hidden" name="to" value="attacker-account" />
    <input type="hidden" name="amount" value="10000" />
  </form>
</body>
</html>

<!-- When a logged-in user visits this page,
     their browser submits the form with their cookies -->

Image-Based Attack

CSRF via image tag
<!-- GET requests can be triggered by images -->
<img src="https://yoursite.com/api/delete-account" style="display:none" />

<!-- Or change settings -->
<img src="https://yoursite.com/api/settings?email=attacker@evil.com" />

Why it works: Browsers attach cookies to requests automatically, regardless of which page triggered the request. Your server receives a request with a valid session cookie and has no way to know it came from somewhere else.

Real-World CSRF Consequences

Attack TargetPotential Damage
Email settingsChange recovery email, take over account
Password changeLock user out of their account
Payment actionsTransfer money, make purchases
Admin functionsAdd attacker as admin, modify data
Social actionsPost content, follow accounts, share data

How to Prevent CSRF

1. Use SameSite Cookies

SameSite cookies don't travel on cross-site requests. It's the simplest fix and it works in all modern browsers:

Setting SameSite cookies
// Express.js session with SameSite
app.use(session({
  cookie: {
    httpOnly: true,
    secure: true, // Requires HTTPS
    sameSite: 'strict' // Or 'lax' for less strict protection
  }
}));

// Next.js API route
res.setHeader('Set-Cookie',
  'session=abc123; HttpOnly; Secure; SameSite=Strict');
SameSite ValueBehaviorUse Case
StrictNever sent cross-siteMaximum security
LaxSent on top-level navigations onlyGood balance (default in modern browsers)
NoneAlways sent (requires Secure)Third-party integrations

2. Use CSRF Tokens

A CSRF token is a secret value your server generates, embeds in a form, and checks on submission. An attacker on another page can't read it from your DOM, so they can't forge a valid request:

CSRF token implementation
// Generate token and store in session
const csrfToken = crypto.randomUUID();
req.session.csrfToken = csrfToken;

// Include in form
<form action="/transfer" method="POST">
  <input type="hidden" name="_csrf" value="{csrfToken}" />
  <!-- other fields -->
</form>

// Verify on submission
if (req.body._csrf !== req.session.csrfToken) {
  return res.status(403).json({ error: 'Invalid CSRF token' });
}

3. Validate the Origin Header

Origin header validation
function validateOrigin(req, res, next) {
  const origin = req.headers.origin || req.headers.referer;
  const allowedOrigins = ['https://yoursite.com'];

  if (origin && !allowedOrigins.some(o => origin.startsWith(o))) {
    return res.status(403).json({ error: 'Invalid origin' });
  }
  next();
}

// Apply to state-changing routes
app.post('/api/*', validateOrigin);

4. Re-authenticate Before Sensitive Actions

Re-authentication for critical actions
// For sensitive actions, require password confirmation
app.post('/api/change-email', async (req, res) => {
  const { newEmail, currentPassword } = req.body;

  // Verify current password before making changes
  const isValid = await verifyPassword(req.user.id, currentPassword);
  if (!isValid) {
    return res.status(401).json({ error: 'Invalid password' });
  }

  // Now safe to change email
  await updateUserEmail(req.user.id, newEmail);
});

CSRF Protection in Frameworks

FrameworkBuilt-in ProtectionHow to Enable
Next.jsNone by defaultUse next-csrf or similar package
ExpressNone by defaultUse csurf middleware
DjangoEnabled by defaultInclude {% csrf_token %} in forms
RailsEnabled by defaultprotect_from_forgery included

Modern browsers help: Modern browsers default to SameSite=Lax for cookies without explicit SameSite setting, which prevents most CSRF attacks on POST requests.

What is CSRF?

CSRF (Cross-Site Request Forgery) is an attack where a third-party page submits requests to your site using a visitor's existing session cookies. The browser attaches those cookies automatically, so your server can't tell the request wasn't intentional.

Do SameSite cookies prevent CSRF?

Mostly yes. SameSite=Strict blocks cookies on all cross-site requests. SameSite=Lax (the browser default since Chrome 80) blocks them on cross-site POST requests but allows them on top-level navigations. For most apps, Lax is enough. Strict is better if you don't mind users not staying logged in when they click a link from another site.

Does using JSON APIs prevent CSRF?

It helps but doesn't eliminate it. Browsers won't send a cross-origin JSON body without CORS approval, so a lot of CSRF vectors don't work. But if your API also accepts application/x-www-form-urlencoded, or if your CORS policy is too permissive, you're still exposed.

What's the difference between CSRF and XSS?

XSS puts malicious code on your page that runs in the victim's browser. CSRF uses the victim's browser to send requests to your site without their knowledge. Different mechanisms, similar damage. Both can lead to account takeover.

Check Your CSRF Protection

Our scanner tests your forms and APIs for CSRF vulnerabilities.

Vulnerability Guides

CSRF Explained: Cross-Site Request Forgery in Plain English