How to Prevent CSRF Attacks in Express

Welcome to this guide on securing your Express applications against Cross-Site Request Forgery (CSRF) attacks. Imagine hosting an exclusive party—you issue personalized invitations (tokens) to each guest so nobody can sneak in with a forged invite. CSRF protection works similarly: each request from your frontend to your backend must present valid “invitations” (tokens), making it much harder for attackers to impersonate a logged-in user.

In this reading, you’ll learn:

By the end, you’ll have a clear roadmap for keeping your application safe from one of the most common exploits on the web.

Review: CSRF Attack Basics

Cross-Site Request Forgery (CSRF) tricks a logged-in user’s browser into making unintentional or malicious requests to your server. The user unwittingly carries out actions that seem legitimate because they’re authenticated. For instance, an attacker might embed a hidden form in a malicious page that, when clicked, sends money from the user’s bank account to the attacker’s.

If you need a refresher, revisit the previous reading about CSRF attacks—learning how they work helps you understand why these prevention steps are necessary.

Backend Setup with csurf Middleware

First, you’ll configure your Express application to automatically generate and verify CSRF tokens. Think of csurf as a bouncer at your party: it hands each guest a personalized pass (token), then checks that pass on every subsequent visit to ensure it’s still valid.

  1. 1. Install csurf:
    npm install csurf
    
  2. 2. Import and configure csurf in your Express server:
    const cookieParser = require('cookie-parser');
    const csrf = require('csurf');
    const express = require('express');
    
    const app = express();
    
    // Parse cookies
    app.use(cookieParser());
    
    // Set up CSRF protection middleware
    app.use(
      csrf({
        cookie: {
          secure: process.env.NODE_ENV === 'production',
          sameSite: process.env.NODE_ENV === 'production' && 'Lax',
          httpOnly: true
        }
      })
    );
    
    // Now every request to your server will receive two cookies:
    // 1) Encrypted CSRF cookie (csurf)
    // 2) Decrypted XSRF-TOKEN cookie
    
    // ... your routes here
    
    const port = 3000;
    app.listen(port, () => {
      console.log('Server listening on port', port);
    });
    

Once csurf is set up, Express will generate two cookies:

These two cookies function like matching puzzle pieces—the server can verify they belong together, preventing outside parties from forging them.

Frontend Setup

Next, your frontend must add the decrypted token (from the XSRF-TOKEN cookie) to the headers of every request that changes data (e.g., POST, PUT, DELETE). That’s like including your personalized invitation in each request, proving you’re an authorized guest.

// A simplified example of reading document.cookie in JavaScript
const cookies = {};

// document.cookie is a string: "key1=value1; key2=value2; ..."
document.cookie.split(';').forEach((pair) => {
  const [key, value] = pair.trim().split('=');
  cookies[key] = value;
});

// Retrieve the decrypted CSRF token
const csrfToken = cookies['XSRF-TOKEN'];

// Include it in any non-GET request
fetch('http://localhost:3000/some-data', {
  method: 'POST',
  headers: {
    'XSRF-Token': csrfToken
  },
  body: JSON.stringify({ example: 'payload' })
})
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

Here’s the flow:

  1. 1. Browser receives two cookies: The encrypted csurf cookie (HTTP-only) and the decrypted XSRF-TOKEN cookie (readable by JavaScript).
  2. 2. Frontend reads the XSRF-TOKEN cookie: Storing the value in a variable (e.g., csrfToken).
  3. 3. Frontend includes XSRF-Token: [token value] in the headers: For POST, PUT, DELETE, or any requests that need protection.
  4. 4. Server verifies both tokens: Ensuring the decrypted XSRF-TOKEN matches the encrypted csurf cookie.
  5. 5. If valid, the server processes the request: Otherwise, it rejects the request, preventing any CSRF attempt.

What to Expect/Require in Every Non-GET Request

By design, GET requests typically don’t alter data, so CSRF checks are more relevant for POST, PUT, DELETE, and other requests that change state or process sensitive info.

Your server will expect:

If these two tokens do not match, csurf rejects the request. Only when both tokens align does the request go through.

Analogy: Matching Keys and Locks

Consider the backend as a locked door with two keyholes:

If both keys fit, the door opens. If either key is missing or incorrect, the door stays locked—this prevents attackers from picking just one of the locks and waltzing inside.

Wrapping Up

You now know how to shore up your Express app against CSRF attacks:

These measures significantly reduce the risk that a malicious site can force an authenticated user to take unwanted actions. While no security system is perfect, CSRF tokens form an essential part of your overall defense strategy in modern web applications.

Congratulations—you’re well on your way to building safer, more secure Express apps!