Express Validator Tutorial

Express Validator Tutorial

Learn how to use Express Validator, a set of middleware functions, to perform server-side validation on user data.

Learn how to use Express Validator, a set of middleware functions, to perform server-side validation on user data.

TL;DR: In this article you will learn to validate and sanitize data in your Express.js applications using a set of middleware called [express-validator](https://express-validator.github.io/docs/ "express-validator"). You will do so by completing a demo application that takes in user input and validates/sanitizes it using this library. If you want to see the complete application developed throughout this article, take a look at this GitHub repository.

Prerequisites

Software you need to install to follow this tutorial:

What Is Express Validator?

According to the official website, Express Validator is a set of Express.js middleware that wraps [validator.js](https://github.com/chriso/validator.js "validator.js"), a library that provides validator and sanitizer functions. Simply said, Express Validator is an Express middleware library that you can incorporate in your apps for server-side data validation.

Why Server-Side Validation?

Mainly speaking, you must care about server-side validation because data coming from other clients (single-page apps, regular web apps, mobile applications, etc.) cannot be trusted. For example, your servers have no means to know if a malicious user (or virus) disabled front-end validation (e.g., JavaScript) to allow the app to submit bogus data to your server. That is, client-side validation is not enough.

Set Up Your Playground For Express Validator

To start playing with Express Validator, you will clone a simple Express server to use as a playground. To do so, open your terminal in a preferred directory and clone the starter project with the following command:

# clone repository
git clone https://github.com/auth0-blog/expressjs-validator.git

# move into your clone repository
cd expressjs-validator


Then, install all the project dependencies by running the following command in the project root:

npm install


This simple Express application contains just enough for you to get started and focus on learning about the express-validator library. For example, if you check the views/layout.pug file, you will see that it contains some basic HTML elements to define how the app should look like.

Note: If you are not familiar with Pug, the template engine, you might find the syntax of this file a bit different. Don’t worry, you don’t need to be acquainted with Pug to follow this article along. Also, if you take a close look to the source code, you will probably understand what is going on. For example, you will see a line starting with link that contains the href keyword pointing to a BootStrap file. This is just the code telling the browser to import an external CSS file, quite similar to what you would do in HTML.
Besides the views/layout.pug file, these are other important files that you must know about:

With that, you now know how the app is structured, but don’t run the project yet! You still need to define the home page of your app.

Build Homepage For User Input

After learning about the structure of the playground, you will need to add the following code to the views/index.pug file:

extends layout

block content
  .container
    .alert(class="alert-danger", role="alert", id="error-group", style="display: none")
      ul(id="errors")
    .row
      .col-sm.col-md-8.offset-md-2.col-lg-6.offset-lg-3
        h3 Welcome To Express Validator!
        form
          .form-group
            input.form-control(type="text", id="name", placeholder="Name")
          .form-group
            input.form-control(type="text", id="classYear", placeholder="Class Year")
          .form-group
            input.form-control(type="text", id="weekday", placeholder="Available Weekday")
          .form-group
            input.form-control(type="email", id="email", placeholder="Email")
          .form-group
            input.form-control(type="password", id="password", placeholder="Password")
          .form-group
            input.form-control(type="password", id="confirmPassword", placeholder="Confirm Password")
          .form-group
            button.form-control(type="button", class="btn btn-primary", id="signup-btn") Submit


What you are doing here is to add a bunch of input elements to the homepage that will receive user input to be sent to the backend. With that covered, you have everything in place to have a sneak peek at the app. Go to the project root directory and run the following command to get the app started.

node app.js


Now, if you visit [http://localhost:3000/](http://localhost:3000/ "http://localhost:3000/") in your favorite browser, you should see the following page:

Note: If you are not familiar with Pug, the template engine, you might find the syntax of this file a bit different. Don’t worry, you don’t need to be acquainted with Pug to follow this article along. Also, if you take a close look to the source code, you will probably understand what is going on. For example, you will see a line starting with link that contains the href keyword pointing to a BootStrap file. This is just the code telling the browser to import an external CSS file, quite similar to what you would do in HTML.
Once the user enters data on your homepage and clicks the Submit button, you will need to handle the on submit event and make an AJAX request to your Express back-end. To do that, add the following code to your ./public/javascripts/script.js file:

$('#signup-btn').click(function () {
  $.ajax({
    url: '/users',
    type: 'POST',
    cache: false,
    data: {
      name: $('#name').val(),
      classYear: $('#classYear').val(),
      weekday: $('#weekday').val(),
      email: $('#email').val(),
      phoneNumber: $('#phoneNumber').val(),
      password: $('#password').val(),
      confirmPassword: $('#confirmPassword').val()
    },
    success: function () {
      $('#error-group').css('display', 'none');
      alert('Your submission was successful');
    },
    error: function (data) {
      $('#error-group').css('display', 'block');
      var errors = JSON.parse(data.responseText);
      var errorsContainer = $('#errors');
      errorsContainer.innerHTML = '';
      var errorsList = '';

      for (var i = 0; i < errors.length; i++) {
        errorsList += '<li>' + errors[i].msg + '</li>';
      }
      errorsContainer.html(errorsList);
    }
  });
});


From the code above, on form submission, a POST request is made to the [http://localhost:3000/users](http://localhost:3000/users "http://localhost:3000/users") endpoint with the data entered by users on the HTML fields defined before. If there is an error during validation, this code will make sure to display the error messages inside a div marked with the alert class. If no errors are found, a successful validation will trigger an alert with this message: “Your submission was successful”.

Server-Side Validation With Express Validator

Now that you have your front-end set up, you will start learning about express-validator. To do that, the first thing you will need to do is to install the library:

npm install express-validator --save


After installing express-validator, open the routes/users.js file and import the check API to use its validation functions:

// ... call to require('express') ...
const {check, validationResult} = require('express-validator/check');

// ... leave the rest untouched ...


Making sure fields are not empty

Now, to validate your form input, you need to pass an array in which you specify the fields that you want to validate as a second argument to your route handler for /users POST requests. To do that, add the following code after the GET route handling code in routes/user.js:

// ... require statements ...

// ... router.get ...

router.post('/', [
    check('name').not().isEmpty().withMessage('Name must have more than 5 characters'),
    check('classYear', 'Class Year should be a number').not().isEmpty(),
    check('weekday', 'Choose a weekday').optional(),
    check('email', 'Your email is not valid').not().isEmpty(),
    check('password', 'Your password must be at least 5 characters').not().isEmpty(),
  ],
  function (req, res) {
    const errors = validationResult(req);
    console.log(req.body);

    if (!errors.isEmpty()) {
      return res.status(422).jsonp(errors.array());
    } else {
      res.send({});
    }
  });

module.exports = router;


The string passed as an argument to the check function specifies which data field you want to validate (e.g., classYear). The not().isEmpty() function chain makes the library validate that this field is not empty. The string argument added to withMessage() specifies a custom error message which will be send back to your users if this particular field violates a validation requirement.

Note: If you are not familiar with Pug, the template engine, you might find the syntax of this file a bit different. Don’t worry, you don’t need to be acquainted with Pug to follow this article along. Also, if you take a close look to the source code, you will probably understand what is going on. For example, you will see a line starting with link that contains the href keyword pointing to a BootStrap file. This is just the code telling the browser to import an external CSS file, quite similar to what you would do in HTML.
By calling the optional() function on it, you have made the weekday field optional. So far, you have only specified how you want your fields to be validated. You might be wondering where Express.js actually does the validation. Validation happens in the request handler when it calls the validationResult() function. To test this, run your application and submit an empty form to the server and you should see the following error displayed in red.

Remark how there is no error for the weekday field, this is because you made the field optional during validation. Pretty cool, huh?

Validating fields’ length

You can also check if a data field meets a certain character length requirement by using the isLength() function. This function takes in an object in which you specify the minimum and/or the maximum number of characters in that field. To see this in action, update the validation requirements for the name and password field as follows:

[
  // ... leave other validations untouched ...
  check('name').not().isEmpty().isLength({min: 5}).withMessage('Name must have more than 5 characters'),
  check('password', 'Your password must be at least 5 characters').not().isEmpty().isLength({min: 5}),
]


Now, restart the app and try to enter a name and password less than five characters long. You should see the app complaining about the field length.

Validating fields’ type

Apart from validating the character length of a field, you can also validate its type or validate it against a list. For example, you can specify that your classYear field needs to be an integer. You can also specify that weekday should not be Saturday or Sunday. To do that, update your validation requirements as follows:

[
  // ... leave other validations untouched ...
  check('classYear', 'Class Year should be a number').not().isEmpty().isInt(),
  check('weekday', 'Choose a weekday').optional().not().isIn(['Sunday', 'Saturday']),
]


Now, after restarting the app, if you try to enter a value which is not an integer in the class year field, you will see an error message. Also, if you enter “Saturday” or “Sunday” in the weekday field, you will see the backend complaining.

Validating and sanitizing emails

Express Validator also validates emails with the isEmail() function. Besides that, you can also make this library normalize emails with the normalizeEmail() function. In case you are wondering what it means to normalize an email, you can read about it in this Stack Overflow post.

In order to validate the email field, refactor the check('email' call in your code:

[
  // ... leave other validations untouched ...
  check('email', 'Your email is not valid').not().isEmpty().isEmail().normalizeEmail(),
]


Then, after restarting the app, if you enter an invalid email on your homepage you will get an error message:

Sanitizing fields with trim and escape

With Express Validator, you can also trim away spaces from your data and escape special characters using the trim() and escape() functions. The escape() function can really come in handy when protecting yourself from cross-site scripting attacks. To add this feature to your app, refactor the call to check('name') as follows:

[
  // ... leave other validations untouched ...

  check('name').exists().isLength({min: 5}).trim().escape().withMessage('Name must have more than 5 characters'),
]


To test this, the starter project came with code that prints every request body to the console after validation. So, imagine a malicious user types the following JavaScript code in your name field: <script> alert("hello, there");</script>. By using escape you will have no problem, as the name that you will get in your backend will look like:

<script> alert("hello, there");<&#x2F;script>


That is, Express Validator transformed the malicious code into special HTML characters. Amazing, right?

Validating with custom validators

Although Express Validator offers a bunch of solutions out of the box for you, chances are you will need to implement some custom validation. For example, imagine you need to make sure that the password field matches the confirmPassword field. To do that, you can use the custom() validation function as follows:

[
  // ... leave other validations untouched ...

  check('confirmPassword', 'Passwords do not match').custom((value, {req}) => (value === req.body.password)),
]


The custom function takes in a function (an arrow function in this case) as an argument and checks if the value of the confirmPassword is the same as the value of the req.body.password field. To see this in action, restart your app, then enter a different password and you should get the message saying that “passwords do not match”.

Conclusion

In this article, you learned how to use the Express Validator middleware to validate and sanitize user data in an Express application. Guess what? You only scratched the surface of what is possible with Express Validator. Aside from learning more validation and sanitization functions, you can learn how to add wildcards to your validations, do schema validation and whole body validation from Express Validator’s documentation. I will love to see what you build with Express Validator.

Secure Node.js, Express.js and PostgreSQL API using Passport.js

Secure Node.js, Express.js and PostgreSQL API using Passport.js

The comprehensive step by step tutorial on building secure Node.js, Express.js, Passport.js, and PostgreSQL Restful Web Service

The comprehensive step by step tutorial on building secure Node.js, Express.js, Passport.js, and PostgreSQL Restful Web Service. Previously, we have shown you a combination of Node.js, Express.js, and PostgreSQL tutorial. Now, we just add a security for that RESTful Web Service endpoints. Of course, we will start this tutorial from scratch or from zero application. We will use JWT for this Node.js, Express.js, Passport.js, and PostgreSQL tutorial.

Table of Contents:

The following tools, frameworks, and modules are required for this tutorial:

We assume that you have installed PostgreSQL server in your machine or can use your own remote server (we are using PostgreSQL 9.5.13). Also, you have installed Node.js in your machine and can run node, npm or yarn command in your terminal or command line. Next, check their version by type this commands in your terminal or command line.

node -v
v8.12.0
npm -v
6.4.1
yarn -v
1.10.1

That the versions that we are uses. Let’s continue with the main steps.

1. Create Express.js Project and Install Required Modules

Open your terminal or node command line the go to your projects folder. First, install express generator using this command.

sudo npm install express-generator -g

Next, create an Express.js app using this command.

express secure-node --view=ejs

This will create Express.js project with the EJS view instead of Jade view template because using ‘–view=ejs’ parameter. Next, go to the newly created project folder then install node modules.

cd secure-node && npm install

You should see the folder structure like this.

There’s no view yet using the latest Express generator. We don’t need it because we will create a RESTful API.

2. Add and Configure Sequelize.js Module and Dependencies

Before installing the modules for this project, first, install Sequelize-CLI by type this command.

sudo npm install -g sequelize-cli

To install Sequelize.js module, type this command.

npm install --save sequelize

Then install the module for PostgreSQL.

npm install --save pg pg-hstore

Next, create a new file at the root of the project folder.

touch .sequelizerc

Open and edit that file then add this lines of codes.

const path = require('path');

module.exports = {
&nbsp; "config": path.resolve('./config', 'config.json'),
&nbsp; "models-path": path.resolve('./models'),
&nbsp; "seeders-path": path.resolve('./seeders'),
&nbsp; "migrations-path": path.resolve('./migrations')
};

That files will tell Sequelize initialization to generate config, models, seeders and migrations files to specific directories. Next, type this command to initialize the Sequelize.

sequelize init

That command will create config/config.json, models/index.js, migrations and seeders directories and files. Next, open and edit config/config.json then make it like this.

{
&nbsp; "development": {
&nbsp; &nbsp; "username": "djamware",
&nbsp; &nbsp; "password": "[email&nbsp;protected]@r3",
&nbsp; &nbsp; "database": "secure_node",
&nbsp; &nbsp; "host": "127.0.0.1",
&nbsp; &nbsp; "dialect": "postgres"
&nbsp; },
&nbsp; "test": {
&nbsp; &nbsp; "username": "root",
&nbsp; &nbsp; "password": "[email&nbsp;protected]@r3",
&nbsp; &nbsp; "database": "secure_node",
&nbsp; &nbsp; "host": "127.0.0.1",
&nbsp; &nbsp; "dialect": "postgres"
&nbsp; },
&nbsp; "production": {
&nbsp; &nbsp; "username": "root",
&nbsp; &nbsp; "password": "[email&nbsp;protected]@r3",
&nbsp; &nbsp; "database": "secure_node",
&nbsp; &nbsp; "host": "127.0.0.1",
&nbsp; &nbsp; "dialect": "postgres"
&nbsp; }
}

We use the same configuration for all the environment because we are using the same machine, server, and database for this tutorial.

Before run and test connection, make sure you have created a database as described in the above configuration. You can use the psql command to create a user and database.

psql postgres --u postgres

Next, type this command for creating a new user with password then give access for creating the database.

postgres-# CREATE ROLE djamware WITH LOGIN PASSWORD '[email&nbsp;protected]@r3';
postgres-# ALTER ROLE djamware CREATEDB;

Quit psql then log in again using the new user that previously created.

postgres-# \q
psql postgres -U djamware

Enter the password, then you will enter this psql console.

psql (9.5.13)
Type "help" for help.

postgres=>

Type this command to creating a new database.

postgres=> CREATE DATABASE secure_node;

Then give that new user privileges to the new database then quit the psql.

postgres=> GRANT ALL PRIVILEGES ON DATABASE secure_node TO djamware;
postgres=> \q

3. Create or Generate Models and Migrations

We will use Sequelize-CLI to generating a new model. Type this command to create a model for Products and User model for authentication.

sequelize model:create --name Product --attributes prod_name:string,prod_desc:string,prod_price:float
sequelize model:create --name User --attributes username:string,password:string

That command creates a model file to the model’s folder and a migration file to folder migrations. Next, modify models/user.js and then import this module.

var bcrypt = require('bcrypt-nodejs');

Add the new methods to the User model, so the user.js class will be like this.

module.exports = (sequelize, DataTypes) => {
&nbsp; const User = sequelize.define('User', {
&nbsp; &nbsp; username: DataTypes.STRING,
&nbsp; &nbsp; password: DataTypes.STRING
&nbsp; }, {});
&nbsp; User.beforeSave((user, options) => {
&nbsp; &nbsp; if (user.changed('password')) {
&nbsp; &nbsp; &nbsp; user.password = bcrypt.hashSync(user.password, bcrypt.genSaltSync(10), null);
&nbsp; &nbsp; }
&nbsp; });
&nbsp; User.prototype.comparePassword = function (passw, cb) {
&nbsp; &nbsp; bcrypt.compare(passw, this.password, function (err, isMatch) {
&nbsp; &nbsp; &nbsp; &nbsp; if (err) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return cb(err);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; cb(null, isMatch);
&nbsp; &nbsp; });
&nbsp; };
&nbsp; User.associate = function(models) {
&nbsp; &nbsp; // associations can be defined here
&nbsp; };
&nbsp; return User;
};

For the models/product.js there’s no action needed, leave it as default generated the model class.

4. Create Routers for RESTful Web Service and Authentication

To authenticating users and secure the resources or endpoint create this file as a router.

touch routes/api.js

Open and edit routes/api.js then declares all require variables.

const express = require('express');
const jwt = require('jsonwebtoken');
const passport = require('passport');
const router = express.Router();
require('../config/passport')(passport);
const Product = require('../models').Product;
const User = require('../models').User;

Create a router for signup or register the new user.

router.post('/signup', function(req, res) {
&nbsp; console.log(req.body);
&nbsp; if (!req.body.username || !req.body.password) {
&nbsp; &nbsp; res.status(400).send({msg: 'Please pass username and password.'})
&nbsp; } else {
&nbsp; &nbsp; User
&nbsp; &nbsp; &nbsp; .create({
&nbsp; &nbsp; &nbsp; &nbsp; username: req.body.username,
&nbsp; &nbsp; &nbsp; &nbsp; password: req.body.password
&nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; .then((user) => res.status(201).send(user))
&nbsp; &nbsp; &nbsp; .catch((error) => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log(error);
&nbsp; &nbsp; &nbsp; &nbsp; res.status(400).send(error);
&nbsp; &nbsp; &nbsp; });
&nbsp; }
});

Create a router for sign in or login with username and password.

router.post('/signin', function(req, res) {
&nbsp; User
&nbsp; &nbsp; &nbsp; .find({
&nbsp; &nbsp; &nbsp; &nbsp; where: {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; username: req.body.username
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; .then((user) => {
&nbsp; &nbsp; &nbsp; &nbsp; if (!user) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return res.status(401).send({
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message: 'Authentication failed. User not found.',
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; user.comparePassword(req.body.password, (err, isMatch) => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(isMatch && !err) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var token = jwt.sign(JSON.parse(JSON.stringify(user)), 'nodeauthsecret', {expiresIn: 86400 * 30});
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; jwt.verify(token, 'nodeauthsecret', function(err, data){
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log(err, data);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; res.json({success: true, token: 'JWT ' + token});
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; res.status(401).send({success: false, msg: 'Authentication failed. Wrong password.'});
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; .catch((error) => res.status(400).send(error));
});

Create a secure router to get and post product data.

router.get('/product', passport.authenticate('jwt', { session: false}), function(req, res) {
&nbsp; var token = getToken(req.headers);
&nbsp; if (token) {
&nbsp; &nbsp; Product
&nbsp; &nbsp; &nbsp; .findAll()
&nbsp; &nbsp; &nbsp; .then((products) => res.status(200).send(products))
&nbsp; &nbsp; &nbsp; .catch((error) => { res.status(400).send(error); });
&nbsp; } else {
&nbsp; &nbsp; return res.status(403).send({success: false, msg: 'Unauthorized.'});
&nbsp; }
});

router.post('/product', passport.authenticate('jwt', { session: false}), function(req, res) {
&nbsp; var token = getToken(req.headers);
&nbsp; if (token) {
&nbsp; &nbsp; Product
&nbsp; &nbsp; &nbsp; .create({
&nbsp; &nbsp; &nbsp; &nbsp; prod_name: req.body.prod_name,
&nbsp; &nbsp; &nbsp; &nbsp; prod_desc: req.body.prod_desc,
&nbsp; &nbsp; &nbsp; &nbsp; prod_price: req.body.prod_price
&nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; .then((product) => res.status(201).send(product))
&nbsp; &nbsp; &nbsp; .catch((error) => res.status(400).send(error));
&nbsp; } else {
&nbsp; &nbsp; return res.status(403).send({success: false, msg: 'Unauthorized.'});
&nbsp; }
});

Create a function for extract the token.

getToken = function (headers) {
&nbsp; if (headers && headers.authorization) {
&nbsp; &nbsp; var parted = headers.authorization.split(' ');
&nbsp; &nbsp; if (parted.length === 2) {
&nbsp; &nbsp; &nbsp; return parted[1];
&nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; return null;
&nbsp; &nbsp; }
&nbsp; } else {
&nbsp; &nbsp; return null;
&nbsp; }
};

Finally, export the router as a module.

module.exports = router;

5. Run and Test Secure Node.js, Express.js, Passport.js, and PostgreSQL Web Service

To run and test this secure Node.js, Express.js, Passport.js, and PostgreSQL Web Service, run the PostgreSQL instance first then run this command from the Terminal.

nodemon

or

npm start

To test the secure Product endpoint, open the Postman then type fill all required fields like this image.

You should get the response message Unauthorized and status code 401. Next, test signup using the Postman by changing the method to POST, add the address localhost:3000/api/signup, add the header Content-type with value application/json and the body of request raw text like this.

{ "username":"[email&nbsp;protected]", "password":"qqqq1111" }

You should get this response when executing successfully.

Next, test to log in with the above signed/registered username and password by changing the URL to localhost:3000/api/signin. You should get this response when executes successfully.

Now, you can back using the previous GET method with additional header using the token get from the sign in/log in response. You should see the Product data like below.

That it’s, the secure Node.js, Express.js, Passport.js, and PostgreSQL Web Service. You can get the working source code from our GitHub.

Learn More

The Complete Node.js Developer Course (2nd Edition)

Learn and Understand NodeJS

Node JS: Advanced Concepts

GraphQL: Learning GraphQL with Node.Js

Angular (Angular 2+) & NodeJS - The MEAN Stack Guide

The Complete Python & PostgreSQL Developer Course

SQL & Database Design A-Z™: Learn MS SQL Server + PostgreSQL

The Complete SQL Bootcamp

The Complete Oracle SQL Certification Course

Security Best Node.js - Managing Sessions in Express.js

Security Best Node.js - Managing Sessions in Express.js

Every user interaction with your application is an isolated and individual request and response. The need to persist information between requests is vital for maintaining the ultimate experience for the user and the same applies for Node.js web...

Every user interaction with your application is an isolated and individual request and response. The need to persist information between requests is vital for maintaining the ultimate experience for the user and the same applies for Node.js web application such as the popular Express.js framework.

A common scenario that most developers can identify with is the need to maintain that a user has authenticated with an application.  In addition, it’s quite common to retain various personalized user information that is associated with a session as well.

Similarly, we are going to look at how we can securely set up sessions in our application to mitigate risks such as session hijacking. We’re going to look at how we can obfuscate session ID’s, enforce a time-to-live in our sessions, set up secure cookies for transporting sessions, and finally the importance and role of Transport Layer Security (TLS) when it comes to using sessions.

Setting of Sessions in Express.js

We’re going to use the NPM module express-sessions, a very popular session module that has been highly vetted by the community and constantly improved.

We’ll pass our express app object to a function to wire up the express-session module:

"use strict";
// provides a promise to a mongodb connection
import connectionProvider            from "../data_access/connectionProvider";
// provides application details such as MongoDB URL and DB name
import {serverSettings}              from "../settings";
import session                       from "express-session";
import mongoStoreFactory             from "connect-mongo";

export default function sessionManagementConfig(app) {

    // persistence store of our session
    const MongoStore = mongoStoreFactory(session);

    app.use(session({
        store: new MongoStore({
            dbPromise: connectionProvider(serverSettings.serverUrl, serverSettings.database)
        }),
        secret: serverSettings.session.password,
        saveUninitialized: true,
        resave: false,
        cookie: {
            path: "/",
        }
    }));

    session.Session.prototype.login = function(user, cb){
        this.userInfo = user;
        cb();
    };
}
What’s Going On Here

We're importing the session function from the express-session NPM module and passing the session function a configuration object to set properties such as:

  • **Store. **I’m using MongoDB as my backend, and I want to persist the application sessions in my database, so I am using the connect-mongo NPM module and setting the session store value to an instance of this module. However, you might be using a different backend, so your store option could be different. The default for express-session is an in-memory storage.

  • Secret. This is a value used in the signing of the session ID that is stored in the cookie.

  • Cookie. This determines the behavior of the HTTP cookie that stores the session ID.

We will come back to some of the elements I didn’t mention here shortly.  For now, let’s look at the first change we need to make with securely managing user sessions in our application.

Session Hijacking

The most prevalent risk that user sessions face is session hijacking. Sessions are much like a driver’s license or passport and provide identification for our users. If an attacker can steal the session of another user, they have essentially become that other person and can exploit the user or perform malicious activity on behalf of that user. The risk is even greater when the identity is someone with escalated privilege such as a site admin.

Session Identification

The first step that any attacker will perform is reconnaissance to determine where vulnerabilities lie in your application. Part of that reconnaissance is observing tell-tale signs of the underlying framework, third-party modules and any other software that in itself might contain vulnerabilities that can be exploited.

In the case of our sessions, a tell-tale sign is the name of the session cookie connect.sid, which can help an attacker identify the session mechanism being used and look for specific vulnerabilities.

Tip: Of course we don’t want to use vulnerable software. However, secure software today could be vulnerable tomorrow with a faulty update. So, keeping details about our application obfuscated can help make it that much more difficult for an attacker to exploit.

Therefore, the first thing we want to do is make that as hard to determine what session mechanism is used as possible, let’s update our session configuration object with a name property:

 app.use(session({
        store: new MongoStore({
            dbPromise: connectionProvider(serverSettings.serverUrl, serverSettings.database)
        }),
        secret: serverSettings.session.password,
        saveUninitialized: true,
        resave: false,
        cookie: {
            path: "/",
        }

       name: "id"
    }));

What Did We Do?

By providing a name property with a value of ID, it will be that much more difficult for any attacker to determine the underlying mechanisms used by our application.

Now that we have provided a level of obfuscation to our sessions, let’s look at how we can reduce the window of opportunity for session hijacking.

Session Time-to-Live

Unfortunately, sometimes the best-laid plans can be undermined. A perfect example is a user who didn’t log off a public computer and an attacker who was able to physically obtain access and operate as the previous user.

Therefore, we can reduce the window that the session is alive and directly impact the chances that an attacker can exploit a user or the system from a hijacked session by limiting the life of a session.

As I mentioned before, the express-sessions NPM module provides a store property where you can set a separate storage mechanism for storing your sessions (the default is in-memory). Therefore, the following change is tied to your backend storage of sessions. In my case, I am storing my sessions in a MongoDB database, and using the NPM module connect-mongo for easily storing sessions in the database.

In this case, I can provide the ttl property a value in seconds in the configuration object provided to the MongoStore, the default is 14 days (142460*60):

app.use(session({
   store: new MongoStore({
       dbPromise: connectionProvider(serverSettings.serverUrl, serverSettings.database),
       ttl: (1 * 60 * 60)
   }),
   //remaining - removed for brevity
}));

Cookie Time-to-Live

Not nearly as important as the session’s TTL, we can also set the expiration of the cookie, which is used for transporting the session ID, in the session configuration object.
We can provide a maxAge property and value in milliseconds in the cookie object.

app.use(session({
       //..previous removed for brevity

        cookie: {
            path: "/“,
       maxAge:  1800000  //30 mins
        }
}));

However, security and user experience is always a balancing act and in this case, reducing the time-to-live of the session will directly affect the user experience of when the user will be required to re-authenticate.

Tip: The most important thing is the life of the session, so whether you set a cookie’s age, you should never rely on it by itself and should always regulate the session’s time-to-live. One way to counterbalance the user’s experience is to require re-authenticating at the time that user’s attempt to access key access area’s of your site.

Regenerating New Sessions

It’s common practice to associate a session with an anonymous user, one who hasn’t authenticated with your application. However, when a user does successfully authenticate with your application, it is absolutely paramount that the authenticated user doesn’t continue to use the same session ID.

There are a number of creative ways attackers can obtain an authenticated user’s session, especially when sites make it easy by transporting the session ID in the URL.  In the case that an unauthenticated session had been hijacked by an attacker, when the legitimate user authenticates with the site, and the site has allowed the user to continue using the same session ID, the malicious user will also find that the session they had hijacked is now an authenticated session, allowing them to operate as the user.

You’ll notice in the original function where we wired up our Sessions, we’re adding a Login method to the Session’s prototype so it’s available to all instances of a Session object.

session.Session.prototype.login = function(user){
        this.userInfo = user;
};

This is simply a convenience method that I can access to associate user information with a user session, such as in the case of an authentication route API. For example, like we saw back in Node.js and Password Storage with Bcrypt, following a successful login, we have access to the session object off the request.

authenticationRouter.route("/api/user/login")
    .post(async function (req, res) {
        try {

          //removed for brevity….

          req.session.login(userInfo, function(err) {
            if (err) {
               return res.status(500).send("There was an error logging in. Please try again later.");
            }
          });
        //removed for brevity….
     });

However, it’s absolutely paramount not to continue using the same session ID after a user has successfully authenticated.  The express-sessions module provides a convenient regenerate method for regenerating the session ID. Our loginprototype method off of the Session object is a convenient place for regenerating the Session ID, so let's update:

session.Session.prototype.login = function (user, cb) {
   const req = this.req;
   req.session.regenerate(function(err){
      if (err){
         cb(err);
      }
   });

   req.session.userInfo = user;
   cb();
};

Now, when we provide information to be associated with a user’s session following the user’s successful authentication with our application, we can also ensure that a new session has been generated for this user.

Since cookies are what we use to transport our sessions, it’s important to implement secure cookies.  Let’s look at how we can do that next.

Cookies

We use sessions to maintain state between user requests and we use cookies to transport the session ID between those requests. You probably don’t drive through shady neighborhoods without locking your doors, nor should you throw your sessions out in the wild without any protection.

There are primarily 3 ways to protect the cookie, 2 that we will look at here, and a third we’ll examine in the next section when we look at serving our application content over HTTPS.

HTTPOnly Flag

Since the session ID is of no use to the client, there is absolutely no reason that the front-end application should ever have access to the session ID in the cookie.  However, by default, any script running on the front-end application will have access to a cookie’s contents.

We can limit access to our session cookie’s content by issuing the HTTP Set-cookie header and specifying the HTTPOnly flag for our session cookie.

HTTP header:

Set-cookie: mycookie=value; path=/; HttpOnly

We can easily provide this header by simply setting the httpOnly property on our cookie object to “true:”

app.use(session({
       //..previous removed for brevity

        cookie: {
            path: “/“,
            httpOnly: true,
       maxAge:  1800000
        }
}));

Now, only the agent (i.e., browser) will have access to the cookie in order to resubmit it on the next request to the same domain. This will directly help mitigate cross-site scripting threats that could have otherwise accessed the contents of our session cookie.

Secure Flag

With the HTTPOnly flag set, we limited application scripts from accessing our cookies at the front-end application, but what we haven’t stopped is prying eyes.

Man-in-the-middle (MitM) attacks are common and can easily be carried out by anyone that has access to the network traffic.  This goes along with any local coffee house WIFI that users might typically use.  If your session information is sent over the network without being encrypted, that information is available to anyone listening to the network traffic.

In addition to the HTTPOnly flag we specified, we can also set the Secure flag on our Set-CookieHTTP Header. This will notify the agent (i.e., browser) that we don’t want to send our cookie on any HTTP requests unless it is a secure connection.

HTTP header:

Set-cookie: mycookie=value; path=/; HttpOnly secure

Again, we can easily do so by setting the secure property on our cookie object to “true:”

app.use(session({
       //..previous removed for brevity

        cookie: {
            path: “/“,
            httpOnly: true,
            secure: true,
            maxAge:  1800000
        }
}));

In addition, a common scenario that the secure flag helps prevent is the risk of mixed content. It’s quite common to easily have a page or site setup to use secure HTTP (HTTPS), but an individual resource on that page is being requested insecurely over HTTP.

Without the Secure flag, that HTTP request would have leaked our session cookie on that insecure request for that resources.

Tip: Content Security Policies can help report when a page consist of mixed content. In a future post, we’ll look at how content security policies (CSP) can help to address mixed-content issues.

Unfortunately, with the Secure flag set, unless we are serving our page or site over HTTPS, we have eliminated the ability to send our session cookie on each request and that’s definitely not something we want. So, in the final section, let’s look at the importance of using Transport Layer Security (TLS) when it comes to securely managing our user sessions.

Transport Layer Security

We mentioned the risk of man-in-the-middle attacks earlier and how anyone with the means can easily listen to all network traffic. However, because the traffic is insecure and not encrypted, they can also eavesdrop on the information in that traffic.

I have already covered the underlying details about TLS and since this isn’t strictly an article on TLS, I’m not going to rehash those details here. However, no topic on session management would be complete without mentioning the role and importance of TLS when it comes to securing user sessions.

If we only ever want to send our highly sensitive session cookies over a secure connection, then we must serve those requests and pages over an HTTP connection using Transport Layer Security (HTTPS).

TLS provides a number of benefits such as integrity of the information being exchanged and validity of the server you’re communicating with. In addition, transport layer security provides confidentiality of the information being exchanged through the means of encryption. Serving your site's content over TLS can ensure that your sensitive session cookie is encrypted and not viewable by the prying eyes of a man-in-the-middle attack.

In a future post, we’ll look at the details of serving our Node.js application over HTTPS.

Conclusion

Sessions are still a highly prominent tool for maintaining user state in a very stateless environment. However, sessions are usually tightly tied to sensitive data such as authentication and identification information regarding the user. Therefore, it is absolutely paramount to implement the necessary mitigations to protect against risks such as session hijacking as well as related threats that can lead to sessions hijacking such as man-in-the-middle and cross-site scripting attacks.

Thank you for reading !

Express.js & Node.js Course for Beginners - Full Tutorial

Learn how to develop a web app from scratch using Node.js, Express.js, PostgreSQL, and Bootstrap. Develop a landing page and a CRUD application collecting sales leads.


The code from this project is a good starting place for you to develop your own project.

💻Code: https://github.com/buzz-software/expressjs-mvp-landing-page