Cross-Site Request Forgery (CSRF) is one of the most notorious web vulnerabilities that developers must defend against. This attack tricks an authenticated user into unknowingly performing unwanted actions on a web application. For example, a malicious website could force a logged-in user to change their email address or transfer funds without their consent. 

Since Node.js is widely used for building scalable web applications, understanding how to protect them from CSRF attacks is crucial.

Understanding CSRF Attacks 

How Does CSRF Work? 

A CSRF attack exploits the trust that a web application has in the user’s browser session. The typical workflow of a CSRF attack is: 

  1. A user logs into a website and remains authenticated via cookies or session tokens. 
  2. The user visits a malicious website while still logged into the legitimate site. 
  3. The malicious website sends a request to the legitimate website using the victim’s authenticated session. 
  4. Since the user’s browser automatically includes authentication cookies, the request is processed as if the user made it intentionally. 

Example of a CSRF Attack 

Let’s say you have a banking application built with Node.js. A user is logged in and has the ability to transfer money by sending a POST request:

 

POST /transfer HTTP/1.1  
Host: bank.com  
Cookie: session_id=12345  
Content-Type: application/json  

{ "amount": 1000, "to_account": "hacker123" }


 

Now, a hacker creates a webpage with a hidden request:

 

action="https://bank.com/transfer" method="POST">
     type="hidden" name="amount" value="1000" />
     type="hidden" name="to_account" value="hacker123" />
     type="submit" value="Click me!" />


 

When the user unknowingly clicks the button, the form submits and transfers money to the attacker’s account because the session cookies are automatically included. 

How to Protect Node.js Applications from CSRF 

There are multiple strategies to protect Node.js applications from CSRF attacks. Let’s go over them in detail. 

1. Use CSRF Tokens 

A CSRF token is a random, unique value generated by the server and included in sensitive requests. The server validates this token before processing any action. 

How to Implement CSRF Tokens in Node.js 

If you are using Express.js, the csurf middleware makes it easy to implement CSRF protection. 

Step 1: Install the csurf Middleware


 

npm install csurf


 

Step 2: Configure csurf in Express.js


 

const express = require('express');
const csrf = require('csurf');
const cookieParser = require('cookie-parser');

const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());

// Enable CSRF Protection
const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);

app.get('/form', (req, res) => {
    res.send(`
        
            ${req.csrfToken()}">
            Submit
        
    `);
});

app.post('/submit', (req, res) => {
    res.send('Form submitted successfully!');
});

app.listen(3000, () => console.log('Server running on port 3000'));


 

In this example: 

  • csrf() generates a unique CSRF token. 
  • The token is included in the form as a hidden input field. 
  • When the form is submitted, the server validates the token. 

2. Use SameSite Cookies 

SameSite cookies prevent browsers from sending authentication cookies with cross-site requests. This blocks CSRF attacks by ensuring that cookies are only sent if the request originates from the same website. 

To enable SameSite cookies in Express sessions, modify your session configuration:

 

const session = require('express-session');

app.use(session({
    secret: 'your_secret_key',
    resave: false,
    saveUninitialized: true,
    cookie: { sameSite: 'strict' }
}));


 

Setting sameSite: 'strict' ensures that cookies are not sent on cross-site requests, preventing CSRF attacks. 

3. Use Secure and HttpOnly Cookies 

Secure cookies prevent attackers from stealing session data over unsecured connections, and HttpOnly cookies prevent JavaScript-based attacks. 

Modify your session configuration as follows:

 

app.use(session({
    secret: 'your_secret_key',
    resave: false,
    saveUninitialized: true,
    cookie: { httpOnly: true, secure: true, sameSite: 'strict' }
}));


 

4. Validate Referrer and Origin Headers 

Web browsers automatically send Referer and Origin headers with requests. You can validate these headers to ensure that requests are coming from trusted sources. 

Example middleware to validate origin headers:

 

const allowedOrigins = ['https://yourwebsite.com'];

app.use((req, res, next) => {
    const origin = req.headers.origin || req.headers.referer;
    if (!origin || allowedOrigins.includes(origin)) {
        return next();
    }
    res.status(403).send('CSRF attack detected!');
});


 

5. Require Authentication for Sensitive Actions 

For actions like fund transfers, password changes, or data modifications, require users to re-authenticate before processing the request. 

Example:

 

app.post('/transfer', (req, res) => {
    if (!req.session.isAuthenticated) {
        return res.status(403).send('Authentication required!');
    }
    // Proceed with transfer logic
});

6. Implement CAPTCHA for Critical Requests 

Using CAPTCHA on sensitive actions like password changes or payments ensures that automated requests cannot be executed by attackers. 

Google’s reCAPTCHA is a popular choice: 

  • Register at Google reCAPTCHA 
  • Integrate the CAPTCHA verification on your forms 
  • Verify responses on the server-side 

7. Use API Rate Limiting 

Limiting the number of requests a user can make reduces the impact of automated CSRF attacks. 

Example with express-rate-limit:

 

npm install express-rate-limit


 

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100 // Limit each IP to 100 requests per window
});

app.use('/transfer', limiter);


 
 

Conclusion 

CSRF attacks can have devastating consequences if left unaddressed. Protecting Node.js applications requires a multi-layered approach, including: 

  • CSRF Tokens to verify legitimate user requests. 
  • SameSite Cookies to prevent cookie transmission across sites. 
  • Secure and HttpOnly Cookies for stronger security. 
  • Referrer and Origin Header Validation to block unauthorized requests. 
  • Re-authentication and CAPTCHA to verify user intent. 
  • Rate Limiting to minimize attack effectiveness. 

You may also like:

  1. 10 Common Mistakes with Synchronous Code in Node.js

  2. Why 85% of Developers Use Express.js Wrongly

  3. Implementing Zero-Downtime Deployments in Node.js

  4. 10 Common Memory Management Mistakes in Node.js

  5. 5 Key Differences Between ^ and ~ in package.json

  6. Scaling Node.js for Robust Multi-Tenant Architectures

  7. 6 Common Mistakes in Domain-Driven Design (DDD) with Express.js

  8. 10 Performance Enhancements in Node.js Using V8

  9. Can Node.js Handle Millions of Users?

  10. Express.js Secrets That Senior Developers Don’t Share

Read more blogs from Here

Share your experiences in the comments, and let’s discuss how to tackle them!

Follow me on Linkedin