Getting Started With Express

Getting Started With Express

Express.js, or simply Express, is a web application framework for Node.js, released as free and open-source software under the MIT License.

Express.js, or simply Express, is a web application framework for Node.js, released as free and open-source software under the MIT License.

In last weeks post I went over what the MERN stack is. Today we will begin development by creating our backend which will be written in Express.

If I were to give a brief overview of what Express is, I would summarize it as a framework in Node.Js that allows an easier way to define our routes as opposed to writing and defining our routes in Node.JS itself.

To get started let’s create an empty directory and in terminal we will want to be inside this directory. We will then run the command npm init which will create the package.jsonamongst other files. After we do this we will need to install the express package. To do this we will need to run the command npm install express --save One more package we should install now is nodeman which I will talk about later, to do this lets do npm install nodemon .

In summary so far we should do the following:

  1. npm init
  2. npm install express
  3. npm install nodemon

Now we can start our Express project. Let’s now start by creating a folder called src within out express folder. This will hold all of our files pertaining to our backend ie our Models, Routes, and Controllers. Here we should create a file called index.js where it should contain the following:

// load express module which returns a function
const express = require('express')
//express return value is object of type express
const app = express()
const port = process.env.PORT || 3000
app.listen(port, ()=> console.log(`Listen on ${port}`))

The following lines are considered the boiler plate in order to get the app up and running. With this created all thats left to see if it is working is to create our routes.

In HTML there are 5 verbs.

  1. POST- creates a new resource
  2. PUT- modify the collection itself
  3. GET-returns a list of resources or a single resource
  4. PATCH- replaces every resource in this collection
  5. DELETE-removes the collection

These verbs can be seen when we define our routes in Express see below for how we define our routes in Express:

app.get()
app.post()
app.put()
app.delete()

For this guide we will be focusing on GET requests to create a simple web page. A GET request takes two arguments, it takes the path or url, and a callback function.

The call back function is called when the get request to the endpoint is made. This callback function should take 2 arguments. It should take the request and the response. Putting all this information together we can make a simple get request to our root view "/" by doing the following in the index.js.

 app.get("/",(req, resp)=> {
 resp.send("Hello World!!!")
 })

So now we are going to run our Express Server. In terminal we will do nodemon src/index.js. The reason we will be using nodemon is because if we run node we will have to keep restarting our server. With nodemon our server will always monitor our changes and update accordingly. Now if we go to the browser and visit you should see the resp.send message we have created.

So now that we have our root directory defined, lets create a route that will give us a list of people to be as follows:

app.get("/people",(req, resp)=> {
 resp.send(["bob","jim","joe"])
 })

If we visit "/people" we should get an array with those names. Let’s take this a step further and define a route with param. This route should lead to an individual person. We can do this by doing the following:

app.get("/people/:person",(req, resp)=> {
 resp.send(`Hello ${req.param.person}`)
 })

We have now learned how define our routes for some of our get requests, but we have not learned how to create our 404 or 500 errors.

To create a page to handle 404 errors is quite simple. We can do the following

app.use ((req,res,next) =>{
    res.status(404).send(`Your lost`)
})

If we try to visit a route not defined we will see Your lost display in browser now.

Let’s handle our 500 error differently and actually render an html file. To start lets create a folder called public outside of our src folder. In the public folder lets create a file called 500.html. and add the following:

<!DOCTYPE html>
<html lang="en" dir="ltr">
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        something is wrong
    </body>
</html>

With this now created, lets go back to the index.js and reference this. Before we do we need to reference path which is a library in Express. We can do this by adding:

let path = require(`path`)

Now we can reference the path in our get request. The get request for the 500 error should look like the following:

app.use((err,req,res,next)=>{
    console.error(err.stack)
    res.sendFile(path.join(__dirname, `../public/500.html`))
})

We have now accomplished created all of our error handling pages and have learned how to create get request to render certain pages. Next week we will learn how to create an API with MongoDB.

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