Express.js Security Tips

Express.js Security Tips

TL;DR

This text is part of my new book Pro Express.js: Master Express.js—The Node.js Framework For Your Web Development [Apress, 2014]. Security is important, that’s why I decided to publish this chapter on my blog. The book will be released very soon.

The set of tips in this chapter deals with security in Express.js applications. Security is often a neglected topic that is deferred until the last minute before the release. Obviously, this approach of treating security as an afterthought is prone to leaving holes for attackers. A better approach is to consider and implement security matters from the ground up.

Browser JavaScript has earned a bad reputation for security vulnerabilities, so we need to keep our Node.js apps as secure as possible! With the simple modifications and middleware covered in this chapter, you can effortlessly address some basic security concerns.

This chapter covers the following topics:

  • Cross-site request forgery (CSRF)
  • Process permissions
  • HTTP security headers
  • Input validation

Cross-Site Request Forgery

CSRF and the csurf middleware were briefly covered in Chapter 4 of Pro Express.js. Please refer to that chapter for the CSRF definition and explanation.

The csurf middleware does most of the job of matching incoming values from requests. However, we still need to expose the values in responses and pass them back to the server in templates (or JavaScript XHRs). First, we install the csurf module like any other dependency with:

$ npm install csurf@1.6.0

Then, we apply csurf with app.use(), as covered in Chapter 4:

app.use(csrf());

The csrf must be preceded by cookie-parser and express-session because it depends on these middleware (i.e., to install, import, and apply the necessary modules).

One of the ways to implement the validation is to use custom middleware to pass the CSRF token to all the templates using response.local. This custom middleware must precede the routes (as is the case for most middleware statements):

app.use(function (request, response, next) {
  response.locals.csrftoken = request.csrfToken();
  next();
});

In other words, we manually facilitate the presence of the token in the body (shown in this example), query, or header. (Depending on your preference or a contract between the client, you can use the query or header.)
So, to render the value in the template as a hidden form value, we can use

input(type="hidden", name="_csrf", value="#{csrftoken}")

This hidden input field will add the token value to the submitted form data, facilitating the sending of the CSRF token to the /login route along with other fields such as email and password.

Here’s the full Jade language content in file ch15/index.jade:

doctype html
html
  head
    title= title
    link(rel='stylesheet', href='https://azat.wpenginepowered.com/css/style.css')
  body
    if errors
      each error in errors
        p.error= error.msg
    form(method="post", action="/login")
      input(type="hidden", name="_csrf", value="#{csrftoken}")
      input(type="text", name="email", placeholder="hi@webapplog.com")
      input(type="password", name="password", placeholder="Password")
      button(type="submit") Login
    p
      include lorem-ipsum

To see the demo of CSRF in ch15/app.js, start the server as you typically do with $ node app. Then navigate to the home page located at http://localhost:3000. You should see the token in the hidden field of the form, as shown in Figure 15–1. Keep in mind that your token value will be different but its format will be the same.

CSRF token from the csurf module inserted into the form to be sent later to the /login route
Figure 15–1. CSRF token from the csurf module inserted into the form to be sent later to the /login route

For each request to the home page (/) or refresh of the page, you’ll get a new token. But if you augment the token to simulate the attack (you can do it right there in the Chrome Developer Tools), you’ll get this error:

403 Error: invalid csrf token
  at verifytoken...  

Process Permissions

Obviously, it’s usually a bad idea to run web services as root. Operations developers can utilize Ubuntu’s authbind to bind to privileged ports (e.g., 80 for HTTP and 443 for HTTPS) without giving root access.

Alternatively, it’s possible to drop privileges after binding to a port. The idea here is that we pass the values of GID (group ID) and UID (user ID) to the Node.js app and use the parsed values to set the group identity and user identity of the process. This will not work on Windows, so you might want to use if/else and process.platform or NODE_ENV to make your code cross-platform. Here is an example of dropping privileges by setting GID and UID with properties from the process.env.GID and process.evn.UID environmental vars:

// ... Importing modules
var app = express();
// ... Configurations, middleware and routes 
http.createServer(app).listen(app.get('port'), function(){
    console.log("Express server listening on port "
    + app.get('port'));
    process.setgid(parseInt(process.env.GID, 10));
    process.setuid(parseInt(process.env.UID, 10));
});

HTTP Security Headers

The Express.js middleware called helmet (https://www.npmjs.org/package/helmet; GitHub: https://github.com/ helmetjs/helmet) is a collection of security-related middleware that provides most of the security headers described in the Recx article “Seven Web Server HTTP Headers that Improve Web Application Security for Free.”

As of this writing, helmet is at version 0.4.1 and includes the following middleware:

To install helmet, simply run:

$ npm install helmet@0.4.1

Import the module as you always do:

var helmet = require('helmet');

Then apply the middleware before the routes. The default usage is as follows (ch15/app.js):

app.use(helmet());

Figure 15–2 shows how the helmet v0.4.1 HTTP response will look when used with the default options:

Figure 15–2. helmet v0.4.1 HTTP response when used with default options
Figure 15–2. helmet v0.4.1 HTTP response when used with default options

Input Validation

Express.js doesn’t perform any user/client input sanitation or validation when you use body-parser or query as input data. And, as we all know, we should never trust the input. Malicious code can be inserted (XSS or SQL injections) into your system. For example, browser JavaScript code that you treat as a benign string can turn into an attack when you print that string on your page (especially if your template engine doesn’t escape special characters automatically!).

The first line of defense is to check data manually with regular expressions on the routes that accept external data. The additional “defense” can be added on the object-relational mapping layer, such as the Mongoose Schema (see Chapter 22 of Pro Experss.js).

Remember that front-end/browser validation is performed only for usability purposes (i.e., it is more user-friendly)—it does not protect your web site from anything.

For example, in ch15/app.js we can implement the validation that uses a RegExp pattern on the email field, if-else statements, and the test() method to append an error message to the errors array like this:

app.post('/login-custom', function(request, response){
  var errors = [];
  var emailRegExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

  if (!request.body.password) errors.push({msg: 'Password is required'});
  if (!request.body.email || !emailRegExp.test(request.body.email) ) errors.push({msg: 'A valid
email is required'});
  if (errors)
    response.render('index', {errors: errors});
  else
    response.render('login', {email: request.email});
});

As you add more routes and input fields to validate, you will end up with more RegExp patterns and if/else statements. Although that will work better than having no validation, the recommend approach is to write your own module or use express-validator.

To install express-validator v2.4.0, run:

$ npm install express-validator@2.4.0

Import express-validator in ch15/app.js:

var validator = require('express-validator');

Then apply express-validator after body-parser:

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(validator());

Now, within the request handlers, we get to access request.assert and
request.validationErrors():

app.post('/login', function(request, response){
  request.assert('password', 'Password is required').notEmpty();
  request.assert('email', 'A valid email is required').notEmpty().isEmail();
  var errors = request.validationErrors();
  if (errors)
    response.render('index', {errors: errors});
  else
    response.render('login', {email: request.email});
});

The index.jade file simply prints errors from the array if there are any:

if errors
  each error in errors
    p.error= error.msg

And the login.jade template prints the e-mail. This template is rendered only if the validation went successfully.

 p= email

To demonstrate, go to the home page and try to enter some data. If there are errors, you’ll be shown the home page with errors as shown in Figure 15–3. The double “A valid email is required” message comes from the fact that we have two assertions for the email field (notEmpty and isEmail) and both of them fail when the email field is empty.

Figure 15–3. Error messages from using express-validator to assert form values
Figure 15–3. Error messages from using express-validator to assert form values

Summary

Security is paramount, but often neglected. This is especially true during early stages of development. The typical thought process goes like this: let’s focus on delivering more features, and we’ll take care of security later when we are about to release. This decision is usually well intended but rarely plays out as planned. As the result, the security of the systems suffers.

With middleware libraries such as csurf, helmet, and express-validator, we can get a good amount of basic security without adding too many development cycles.

In the next chapter, we’ll shift gears and cover some approaches to using Express.js with the Socket.IO library for reactive (i.e., updated in real time) views…

If you liked this post, then you might want to explore other excerpts from Pro Express.js: Master Express.js—The Node.js Framework For Your Web Development such as:

The book itself will be sent to print very, very, very soon.

Author: Azat

Techies, entrepreneur, 20+ years in tech/IT/software/web development expert: NodeJS, JavaScript, MongoDB, Ruby on Rails, PHP, SQL, HTML, CSS. 500 Startups (batch Fall 2011) alumnus. http://azat.co http://github.com/azat-co

3 thoughts on “Express.js Security Tips”

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.