Safeguard
Application Security•

How do you protect a Node.js app from CSRF attacks?

Protect a Node.js app from CSRF with SameSite cookies, signed tokens, Origin and Fetch Metadata checks, and no state changes on GET. csurf is archived.

Safeguard Security Team
5 min read

Protect a Node.js app from CSRF by layering defenses: set SameSite on session cookies, require an anti-CSRF token on every state-changing request, verify the Origin header and Sec-Fetch-Site, and never change state on GET. Do not rely on the csurf package, which is archived and no longer maintained.

What is CSRF and why does it still matter?

MITRE's CWE-352 describes the weakness: the web application does not, or cannot, sufficiently verify whether a request was intentionally provided by the user who sent it. The browser attaches cookies automatically, so a page on another site can cause a logged-in user's browser to send a request your server will treat as genuine.

Constructed example: your app has POST /account/email that changes the account email, authenticated by a session cookie. A hostile page contains a hidden form that posts to that URL. A logged-in visitor loads the page, the browser sends the cookie, and the email changes. The attacker then uses password reset.

Is SameSite enough?

It helps a lot and is not sufficient alone. MDN describes SameSite as giving some protection against cross-site request forgery. OWASP's cheat sheet is more specific: the default Lax value allows the cookie on top-level navigations using safe methods such as GET, so if any state-changing operation is reachable by GET, Lax will not stop it. It calls this the most common way SameSite-based defenses fail in practice.

Set your session cookie with SameSite=Lax or Strict, HttpOnly and Secure, and treat it as a second layer. OWASP also warns not to set the cookie for a whole parent domain, since every subdomain would then share it.

Which token pattern should you use?

OWASP calls the synchronizer token pattern one of the most popular and recommended mitigations. The server generates a token tied to the user session, puts it in a hidden form field or a header, and rejects state-changing requests that lack the right value. For stateless designs, OWASP recommends the signed double-submit cookie, where the token is bound to the session with an HMAC, and discourages the naive double-submit variant.

In Express, the csurf middleware used to be the standard answer. The npm registry now marks the package as archived and no longer maintained. Prefer a maintained library or a short implementation of the signed pattern that your team has reviewed, and keep the secret out of source control.

Also protect login. OWASP notes that login CSRF can be mitigated with a pre-session and a token on the login form, and that the pre-session should be replaced by a new session after authentication.

What about APIs and single-page apps?

If your front end calls a JSON API with a session cookie, the same risk applies. OWASP describes two helpful defenses: require a custom request header on AJAX calls, which cross-site forms cannot set without a CORS preflight, and refuse simple content types for state-changing endpoints.

Fetch Metadata gives a lightweight server-side check. The cheat sheet says servers can use the Sec-Fetch-Site header to block obvious cross-site requests, with a sketch that rejects non-safe methods when the value is cross-site. It adds that some legacy browsers do not send these headers, so a fallback to Origin verification is a mandatory part of any implementation.

Use bearer tokens in an Authorization header and you sidestep ambient cookies, though you then take on token storage and XSS concerns. Cross-site scripting defeats most CSRF defenses, so fix both.

How to check or apply this

  1. List every route that changes state. Confirm none uses GET.
  2. Set session cookies with SameSite, HttpOnly and Secure, and scope them to the exact host.
  3. Add a token check, using a signed double-submit or synchronizer pattern, to all POST, PUT, PATCH and DELETE routes.
  4. Verify Origin on those routes, and add a Sec-Fetch-Site check with an Origin fallback.
  5. Require a custom header on API calls from your own front end.
  6. Rotate the session at login and give the login form its own token.
  7. Write one automated test per route that sends a cross-origin request without a token and expects a rejection.
  8. Check your dependencies: replace archived CSRF packages.

Where does Safeguard fit?

Safeguard's code scanning can help find state-changing routes without protection, and dependency scanning will show archived or unmaintained packages in your lockfile. A review of your own design remains necessary.

Quick check

Do JSON APIs need CSRF protection?

If they authenticate with cookies, yes. Custom headers, Origin checks and tokens all apply. APIs that use an Authorization header with bearer tokens are not exposed to ambient-cookie CSRF.

Does CORS stop CSRF?

Not by itself. CORS controls whether a page can read a response. A simple cross-site form post can still be sent, so you need tokens or header checks on the server.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.