← Recipes

阅读中文版本

Protect Against CSRF with Double Submit

Use a same-site cookie plus a double-submit token so state-changing requests cannot be forged from another origin.

~30 minsecuritywebcsrf

Task

Add a double-submit CSRF token paired with SameSite cookies to state-changing endpoints.

When to use this

A state-changing endpoint accepts a session cookie without an anti-CSRF token, so a cross-site form can act as the user.

Prerequisites

  • A web framework with cookie access
  • A front end that can read a cookie
  • State-changing endpoints

Steps

1. Issue a CSRF cookie and require the same token in a header.

res.cookie('csrf', token, { sameSite: 'lax', secure: true, httpOnly: false });
app.use((req, res, next) => {
  if (['POST','PUT','PATCH','DELETE'].includes(req.method)) {
    const header = req.get('x-csrf-token');
    if (!header || header !== req.cookies.csrf) return res.status(403).end();
  }
  next();
});

Expected output: State-changing requests require a header that a cross-site page cannot read, blocking forged requests.

2. Set session cookies to SameSite.

res.cookie('session', sid, { httpOnly: true, secure: true, sameSite: 'strict' });

Expected output: The session cookie is not sent on cross-site navigations, adding defence in depth.

Troubleshooting

Problem

Legitimate cross-site POST breaks

Solution

Use the token exchange flow or documented OAuth redirects rather than relaxing CSRF checks.

Problem

Token readable by scripts

Solution

The CSRF cookie must be readable by your origin scripts but not httpOnly; keep the session cookie httpOnly.

Limitations

  • Double submit relies on cookie scoping being correct.
  • Subdomain attacks can defeat it if cookies are not scoped tightly.
  • Does not replace input validation.

What this recipe does

Add a double-submit CSRF token paired with SameSite cookies to state-changing endpoints.

A state-changing endpoint accepts a session cookie without an anti-CSRF token, so a cross-site form can act as the user.

Prerequisites

  • A web framework with cookie access
  • A front end that can read a cookie
  • State-changing endpoints

Procedure

  1. Issue a CSRF cookie and require the same token in a header.
res.cookie('csrf', token, { sameSite: 'lax', secure: true, httpOnly: false });
app.use((req, res, next) => {
  if (['POST','PUT','PATCH','DELETE'].includes(req.method)) {
    const header = req.get('x-csrf-token');
    if (!header || header !== req.cookies.csrf) return res.status(403).end();
  }
  next();
});

Expected result: State-changing requests require a header that a cross-site page cannot read, blocking forged requests.

  1. Set session cookies to SameSite.
res.cookie('session', sid, { httpOnly: true, secure: true, sameSite: 'strict' });

Expected result: The session cookie is not sent on cross-site navigations, adding defence in depth.

How to confirm it worked

Run the commands above and confirm every expected result in order. If a step does not produce the stated output, use the troubleshooting notes before continuing. A passing run shows a forged cross-site POST without the header is rejected 403 while same-origin requests with the token succeed.

Troubleshooting

Legitimate cross-site POST breaks — Use the token exchange flow or documented OAuth redirects rather than relaxing CSRF checks.

Token readable by scripts — The CSRF cookie must be readable by your origin scripts but not httpOnly; keep the session cookie httpOnly.

Limitations

  • Double submit relies on cookie scoping being correct.
  • Subdomain attacks can defeat it if cookies are not scoped tightly.
  • Does not replace input validation.