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.
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:
crossdomain
: Serves/crossdomain.xml
to prevent Flash from loading certain unwanted content (see www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html)csp
: Adds Content Security Policy that allows for whitelisting of content to load
(see content-security-policy.com and www.html5rocks.com/en/tutorials/security/content-security-policy)hidePoweredBy
: Removes X-Powered-By to prevent revealing that you are using Node.js and Express.jshsts
: Adds HTTP Strict Transport Security to prevent your web site from being viewed on HTTP (instead of HTTPS)ienoopen
: Sets the X-Download-Options header for Internet Explorer 8+ to prevent the loading of untrusted HTML in IE browsers (see blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx)nocache
: Cache-Control and Pragma headers to stop caching (helpful to flush old bugs from users’ browsers)nosniff
: Sets the proper X-Content-Type-Options header to mitigate MIME type sniffing (see msdn.microsoft.com/en-us/library/gg622941%28v=vs.85%29.aspx)xframe
: Sets the X-Frame-Options heading to DENY to prevent your resource from being put
into a frame for clickjacking attacks (see en.wikipedia.org/wiki/Clickjacking)xssFilter
: Sets the X-XSS-Protection header for IE8+ and Chrome to protect from XSS attacks (see blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx)
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:

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.
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.
Very useful- thanks!