csurf is archived — CSRF protection in Express
Express has never shipped CSRF protection. For a decade the answer was csurf, and
that answer is dead: csurf’s last release is 1.11.0, published 2020-01-19, and it
carries an npm deprecation notice saying the package is archived and no longer
maintained.
It still installs. It still works. npm install csurf prints one line of warning and
then behaves exactly as the tutorials say, which is why it is still in production in a
great many applications and still recommended by a great many guides.
This package is archived and no longer maintained. For support, visit https://github.com/expressjs/express/discussionsnpm's deprecation notice for csurf. It prints on install and is the only warning most projects ever get.
Read in: npm registry — csurf@1.11.0 deprecated fieldDecide deliberately, because there is no default
Section titled “Decide deliberately, because there is no default”Unlike the rest of this cluster there is no single correct fix, so here is the honest landscape rather than a pick.
SameSite cookies do most of the work now. sameSite: "lax" — which
express-session sets if you tell it to, and which modern
browsers increasingly default to — means the session cookie is not sent on cross-site
POSTs at all. For a form-based application on a single origin, that closes the classic
attack without any token library. It is not complete: it does nothing for GET requests
that change state (which should not exist anyway), and it depends on the browser.
Token libraries, all currently maintained, if you need defence in depth or support older clients:
| Package | Latest | Pattern |
|---|---|---|
csrf-csrf |
4.0.3 (2025-05-27) | double-submit cookie |
csrf-sync |
4.2.1 (2025-05-10) | synchroniser token, session-backed |
@dr.pogodin/csurf |
1.17.1 (2026-06-07) | maintained fork of csurf’s API |
The fork is the least-disruptive migration if you have csurf wired in already, since
it keeps the API. csrf-csrf is the usual choice for a stateless API.
npm uninstall csurfnpm install csrf-csrf # or csrf-sync, or @dr.pogodin/csurf// Whatever you choose, the SameSite baseline is worth having regardless.app.use( session({ // ... cookie: { secure: true, httpOnly: true, sameSite: "lax" }, }));npm ls csurfAn empty result is what you want. If csurf is present, note that it may be a transitive dependency of something else rather than a direct one — read the tree before assuming it is yours to remove.
curl -si -X POST https://example.com/account/email \ -H 'Cookie: connect.sid=<a-valid-session>' \ -d 'email=attacker@example.test' | head -1A state-changing POST carrying a valid session but no CSRF token, and no
Origin/Referer from your own site, should be rejected. If it succeeds, the
protection is not working regardless of what is installed.
Adding token-based CSRF protection breaks every client that submits without a token — your own AJAX code first, then any mobile app or integration posting to the same endpoints. That is the main reason projects postpone this and then never do it.
Stage it: run the middleware in a report-only or logging mode if the library supports one, or apply it to a single route and expand. Deploying it across every POST at once is how you take checkout down.
If you rely on sameSite: "strict" instead of tokens, remember it also breaks inbound
links from other sites — see session cookies.
Related
Section titled “Related”- Session cookie flags — where
sameSiteis set - Django takes the opposite approach and ships CSRF protection on by default — CSRF_TRUSTED_ORIGINS is what you configure there instead of what you install.