Protect your Node.js app from Cross-Site Request Forgery
Tags: javascript, node, security, csrf
Published: 2018-03-09 14:47:25

---
cover_image: https://thepracticaldev.s3.amazonaws.com/i/jc9hthyanh1e52rtgkqu.png
title: Protect your Node.js app from Cross-Site Request Forgery
published: true
description: Cross Site Request Forgery aka CSRF/XSRF is used by attackers to perform requests on behalf of others. Learn how to protect your Node.js app from it.
tags: javascript, nodejs, security, csrf
canonical_url: https://www.twilio.com/blog/2018/01/protect-your-node-js-app-from-cross-site-request-forgery.html
---

_[Protect your Node.js app from Cross-Site Request Forgery](https://www.twilio.com/blog/2018/01/protect-your-node-js-app-from-cross-site-request-forgery.html) was originally published on the [Twilio Blog](https://www.twilio.com/blog) on January, 2018._

---

﻿One classic attack when working with web applications is [Cross Site Request Forgery aka CSRF/XSRF](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)) (read C-Surf). They are used by attackers to perform requests on behalf of users in your application without them noticing. Let’s look at how they can pull this off and how we can protect our applications from these type of threat.

### Let’s talk theory

![decorative gif](https://thepracticaldev.s3.amazonaws.com/i/6ghdgsdamgvt2xn7yyu2.gif)

Before we can prevent CSRF attacks we need to understand how they work. Typically these attacks are executed on the functionality of web applications that use form-based submissions like `POST` requests and cookie-based authentication.

An attacker places a hidden form into their malicious page that automatically performs a `POST` request to your page’s endpoint. The browser then automatically sends all the cookies stored for that page along with the request. If a user is logged into a current session, the attacker could, for example, post a message on behalf of the logged in user without them noticing. The attacker never has to have access to the page’s cookies for this.

![diagram visualizing a CSRF attack](https://thepracticaldev.s3.amazonaws.com/i/dbcufyw0gocigyrtzxb3.png)

We can protect ourselves from this attack by using CSRF tokens. The concept is that when the browser gets a page from the server, it sends a randomly generated string as CSRF token as a cookie. Later, when your page performs a POST request it will send the CSRF token as a cookie and also in another way such as a parameter in the body or via an HTTP header like `X-CSRF-Token`.

![diagram visualizing CSRF token protection](https://thepracticaldev.s3.amazonaws.com/i/84fgjrf3ho7anat3d3vt.png)

An attacker will not be able to reproduce the same behavior with their hidden form since they won’t be able to access the cookie to retrieve the value and send it along with their malicious POST request.

This concept can be implemented in pretty much any web application but let’s look at how we can implement it in an [Express](https://expressjs.com/) application.

### Getting the board ready

![decorative gif](https://thepracticaldev.s3.amazonaws.com/i/aye2w0gzbbtlzunz5i1w.gif)

First we need an application to see how the CSRF vulnerability works in reality and how we can protect ourselves from it. If you already have an existing [Express](https://expressjs.com/) application, feel free to perform the following steps on it. Alternatively follow the next steps to set up our demo application.

Before we get started make sure you have [Node.js](https://nodejs.org] and [npm](https://npmjs.com) or another package manager installed. Start the new project by running the following commands in your terminal:

```bash
mkdir csrf-demo
cd csrf-demo
npm init -y
npm install express body-parser --save
```

Next create a new file called `index.js` and place the following code into it:

```js
const express = require('express');
const bodyParser = require('body-parser');
 
const PORT = process.env.PORT || 3000;
const app = express();
 
app.use(bodyParser.urlencoded({
  extended: true
}));
 
app.get('/', (req, res) => {
  res.send(`
    <h1>Hello World</h1>
    <form action="/entry" method="POST">
      <div>
        <label for="message">Enter a message</label>
        <input id="message" name="message" type="text" />
      </div>
      <input type="submit" value="Submit" />
    </form>
  `);
});
 
app.post('/entry', (req, res) => {
  console.log(`Message received: ${req.body.message}`);
  res.send(`Message received: ${req.body.message}`);
});
 
app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});
```

Start up the application by running:

```bash
node .
```

Visit [http://localhost:3000](http://localhost:3000) and you should be greeted with `Hello World` and a small form below it.

![screenshot of the web page displaying hello world and an input field](https://thepracticaldev.s3.amazonaws.com/i/m8eux1ibc9cn2dnqizuw.png)

### Dangerous Water

The current server has two endpoints. One is our main page that is served when you go to [http://localhost:3000/](http://localhost:3000/). The other one is a `POST` endpoint on [http://localhost:3000/entry](http://localhost:3000/entry). When we fill out the form and press Submit we’ll make a `POST` request to this endpoint.

Try it by entering some text in the form and press submit. You should see the message returned and it should also be logged into the console of your running server.

![screenshot of terminal window displaying the message output](https://thepracticaldev.s3.amazonaws.com/i/l7so7r1mpc0jrfiaqtxy.png)

Unfortunately an attacker is able to perform the same request on their page. To simulate that we implemented the same form on a [page on Glitch](https://glitch.com/edit/#!/csrf-attack?path=index.html:1:0). Visit [csrf-attack.glitch.me](http://csrf-attack.glitch.me/), type in a message and press submit. The behavior will be the same as submitting the form on the `localhost` page. It will transfer the message and along with it any cookies that are set.

In this case we created a form that the user can submit by themselves but it could have been a hidden form that auto submits with malicious content. Let’s see how we can protect our page from this.

### Going `csurf`ing

There are multiple modules that help you implement CSRF tokens in your application. One of them is [csurf](https://npm.im/csurf). Install that module along with the [cookie-parser](https://npm.im/cookie-parser) dependencies by running:

```bash
npm install cookie-parser csurf --save
```

Both of these modules are middleware that can alter the behavior of a request in Express. We are already using `body-parser` to parse our `POST` body to retrieve the message. Additionally we’ll use it to check for the `_csrf` token. The `cookie-parser` middleware will check that the token is available in the cookies and `csurf` will be the automatic guard for any `POST`, `PUT`, `PATCH` or `DELETE` operations by checking that the `_csrf` token is present in both the cookies and the request body and that they match.

Add the following code to your `index.js` file to configure the middleware:

```js
const express = require('express');
const bodyParser = require('body-parser');
const csurf = require('csurf');
const cookieParser = require('cookie-parser');
 
const PORT = process.env.PORT || 3000;
const app = express();
 
const csrfMiddleware = csurf({
  cookie: true
});
 
app.use(bodyParser.urlencoded({
  extended: true
}));
app.use(cookieParser());
app.use(csrfMiddleware);
 
app.get('/', (req, res) => {
  res.send(`
    <h1>Hello World</h1>
    <form action="/entry" method="POST">
      <div>
        <label for="message">Enter a message</label>
        <input id="message" name="message" type="text" />
      </div>
      <input type="submit" value="Submit" />
      <input type="hidden" name="_csrf" value="${req.csrfToken()}" />
    </form>
  `);
});
 
app.post('/entry', (req, res) => {
  console.log(`Message received: ${req.body.message}`);
  res.send(`CSRF token used: ${req.body._csrf}, Message received: ${req.body.message}`);
});
 
app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});
```

Restart your server and navigate to [http://localhost:3000](http://localhost:3000). Enter some text in the input box and hit `Submit`. You should see the message appear in the console and be greeted in the browser with a message like the one below:

![screenshot of browser displaying a CSRF token and the message submitted](https://thepracticaldev.s3.amazonaws.com/i/4muknc1enqo5hux2q4dt.png)

Now switch back up to the [demo page on Glitch](http://csrf-attack.glitch.me/) and enter a message there. When you hit submit you’ll see that the request failed and that the message won’t appear in the console. The `_csrf` cookie is transferred, however, the page doesn’t send the same value in the `POST` body as `_csrf` value. As a result, the request is blocked by the `csurf` middleware and we have protected ourselves from CSRF attacks.

### What's next?

We’ve seen how to easily integrate CSRF tokens into a Node.js based application with server-side rendered code. However, using CSRF tokens with your front-end frameworks and libraries is just as easy. Since we are sending the token as a cookie, you can just as easily read it and send it as a header with your async requests later. In fact [Angular’s `HttpClient` has this feature already built-in](https://angular.io/guide/http#security-xsrf-protection).

To learn more about how you can secure your Node.js applications make sure to check out my blog post about [Securing your Express app](https://www.twilio.com/blog/2017/11/securing-your-express-app.html). Additionally you should check out the [OWASP page](https://www.owasp.org/index.php/Main_Page) as it covers a wide range of security related topics.

If you have any questions or any other helpful tools to improve the security of your Node.js web applications, feel free to ping me:

- 🐦 Twitter: @dkundel
- 📧 Email: dkundel@twilio.com
- 🐙🐱 GitHub: dkundel

---

_[Protect your Node.js app from Cross-Site Request Forgery](https://www.twilio.com/blog/2018/01/protect-your-node-js-app-from-cross-site-request-forgery.html) was originally published on the [Twilio Blog](https://www.twilio.com/blog) on January, 2018._