Security in Node.js Applications: Addressing Cross-Site Request Forgery (CSRF) attacks

CSRF attacks are similar to XSS attacks in that both occur across multiple sites. In a CSRF attack, malicious software forges a bogus request on another site. To prevent such an attack, CSRF tokens are generated for each page view. The tokens are to be included as hidden values in HTML FORMs and then checked when the FORM is submitted. A mismatch on the tokens causes the request to be denied.

The csurf package is designed to be used with Express https://www.npmjs.com/ package/csurf . In the notes directory, run this:

$ npm install csurf –save 

This installs the csurf package, recording the dependency in package.json. Then install the middleware like so:

import csrf from ‘csurf’;

app.use(cookieParser());

app.use(csrf({ cookie: true })); 

The csurf middleware must be installed following the cookieParser middleware.

Next, for every page that includes a FORM, we must generate and send a token with the page. That requires two things, in the res.render call we generate the token, sending the token with other data for the page, and then in the view template we include the token as a hidden INPUT on any form in the page. We’re going to be touching on several files here, so let’s get started.

In routes/notes.mjs, add the following as a parameter to the res.render call for the /add, /edit, /view, and /destroy routes:

csrfToken: req.csrfToken()

This generates the CSRF token, ensuring it is sent along with other data to the template. Likewise, do the same for the /login route in routes/users.mjs. Our next task is to ensure the corresponding templates render the token as a hidden INPUT.

In views/noteedit.hbs and views/notedestroy.hbs, add the following:

{{#if user}}

<input type=”hidden” name=”_csrf” value=”{{csrfToken}}”>

{{/if}}

This is a hidden INPUT, and whenever the FORM containing this is submitted this value will be carried along with the FORM parameters.

The result is that code on the server generates a token that is added to each FORM. By adding the token to FORMs, we ensure it is sent back to the server on FORM submission. Other software on the server can then match the received token to the tokens that have been sent. Any mismatched token will cause the request to be rejected.

In views/login.hbs, make the same addition but adding it inside the FORM like so:

<form method=’POST’ action=’/users/login’>

<input type=”hidden” name=”_csrf” value=”{{csrfToken}}”>

</form>

In views/noteview.hbs, there’s a form for submitting comments. Make this change:

<form id=”submit-comment” class=”well” data-async data-target=”#rating-modal”

action=”/notes/make-comment” method=”POST”>

<input type=”hidden” name=”_csrf” value=”{{csrfToken}}”>

</form>

In every case, we are adding a hidden INPUT field. These fields are not visible to the user and are therefore useful for carrying a wide variety of data that will be useful to receive on the server. We’ve already used hidden INPUT fields in Notes, such as in noteedit.hbs for the docreate flag.

This <input> tag renders the CSRF token into the FORM. When the FORM is submitted, the csurf middleware checks it for the correctness and rejects any that do not match.

In this section, we have learned how to stop an important type of attack, CSRF.

Source: Herron David (2020), Node.js Web Development: Server-side web development made easy with Node 14 using practical examples, Packt Publishing.

Leave a Reply

Your email address will not be published. Required fields are marked *