CSRF setup for ExpressJs and SSR React/Redux App

dbillinghamuk
3 min readJul 5, 2018

Setup of the csurf ExpressJs middleware in a React application

Introduction

I was recently tasked with adding an extra layer of security to prevent cross site request forgery attacks to an application. This would involve setting up the expressJs csurf middleware to protect POST routes made from the client.

What is a CSRF attack

To summarise, an attacker could create an AJAX button or form on their own site, and trick the user into submitting it, that creates a request against your own API.

More information can be found within the Cross site request forgery (CSRF) section of this blog post and pillarjs Understanding CSRF readme.

How to prevent this type of attack using a CSRF token

Overview

  1. Server sends the client a token and session cookie.
  2. Client sends an XHR request with the session cookie and CSRF token set in the request header.
  3. The server rejects the request if the token is invalid.

Express middleware

Import the csurf middleware into your express application. The options to the module accept either an express-session store or a cookie store. In this example we will use a CSRF cookie to validate the token against. This will also mean the cookie-parser middleware must be called beforehand.

If setting at the top level of your application, all requests apart from GET, HEAD and OPTIONS will be validated, (although this is customisable).

The cookie will automatically be set on the response object. And a function called csrfToken is added to the request which we will need to call later.

//server.jsimport express from 'express';
import cookieParser from 'cookie-parser';
import csurf from 'csurf';
const app = express();app.use(cookieParser());app.use(csurf({
cookie: {
key: '_csrf-my-app',
path: '/context-route',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 3600 // 1-hour
}
}));
//other routes...app.use('/', universalLoader);app.listen(3000);

There are many configuration options you can pass to the cookie property. In this example we customise the;

  • key; cookie name,
  • path; the path the cookie can be read from
  • httpOnly; makes sure that the client can’t read the secret via client-side JavaScript
  • secure; which sets if the cookie should only be read over a HTTPS connection
  • maxAge; which sets an expiry for the cookie

More options can be found within the express documentation.

Server side rendered react/redux setup

We need to be able to pass a CSRF token, from the server to the client on each page request.

We already have a universal/isomorphic renderer in place on the server which takes the request.url, creates a redux store, renders the JSX to string and returns to the client.

So all we want to do is add the CSRF token to the store along with any other initial state:

//universalLoader.jsimport { createStore } from 'redux';
universalLoader = (req, res) => {
...
const store = createStore(
rootReducer,
Object.assign(initialState, { csrf: req.csrfToken() })
);
...
}

We will now see the CSRF token in the root of the store within the redux dev tools.

Client side react setup

Now that the CSRF token and session cookie are available on the client, we need to make sure we are passing that information on any XHR which POSTs data.

//actions.jsconst saveData = async (dispatch, getState) => {
try {
const response = await fetch('/api/save', {
method: 'POST',
credentials: 'same-origin',
headers: new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json',
'CSRF-Token': getState().csrf
}),
body: {}
});
if (response.ok) {
try {
return await response.json();
} catch (err) {}
}
} catch (err) {}
}

Two important things to note within this action is the credentials property, set within the fetch options and the CSRF-Token header property. The credentials property sets that cookies from the same origin are sent along with the request. And the CSRF-Token header property has a value of the CSRF token pulled from redux state.

Conclusion

When a request to /api/save is now executed, (or any other route which accepts a POST request), the middleware will use the session cookie and CSRF token to validate the request. If valid the request will be allowed to continue. If the validation fails then a 403-ForbiddenError: invalid csrf token is returned by default.

References

--

--

dbillinghamuk

Software dev — Javascript, node, express, mongo, react, redux, rxjs, es6, ramda