Click here to Skip to main content
15,879,535 members
Articles / Web Development / Node.js

A Node.js Web Application Security Using Passport, Express-Sessions, JSON Web Tokens, Angular.js And MySQL

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
2 Feb 2020CPOL35 min read 11.5K   209   2  
A Simple Web Application User Authentication Project using Passport.js And JSON Web Tokens (JWT) explained

You can also try out this web application by visiting http://nodejspassportauth.eastus.cloudapp.azure.com/.


Table of Contents

Introduction

“One of the most important investments that you can make in a system, company, or application is in your security and identity infrastructures.” – Johnathan LeBlanc

For the past decade, the World Wide Web has drastically changed the way people are accessing and using the information. Nowadays, the Web is primarily used as a permanent information resource by more than half of the people around the world. Insensibly, the Web has evolved from a purely static information resource into the fully dynamic and highly functional web applications for accomplishing a widespread of real-world tasks.

The Web development technologies and tools of a new era provide an ability to create the advanced web applications that interact with users to store their personal information and private content on the Web or in the cloud, as well as to access and use the information based on their personalized preferences.

Today’s Web is not a safe place, putting most of its users at risk that a private or sensitive data stored under their profiles can be illegally accessed by the other users consuming the same web application, at any time.

The security vulnerability is a core problem of any existing web application, published on the Web. The hacker attacks against web applications normally might cause serious threats to the organization deploying a web application, as well as to the users that access those applications. An interaction with a web application can be malicious due to arbitrary input data submitted by a user to the web application’s server. This includes any attempts to submit forged credentials to access a user’s profile, the various kinds of attacks to the web application server middleware endpoints.

The cross-site requests forgery (CSRF) is one of the ugliest web application attacks, by initiating the billions of forgery requests, submitting any kind of malicious or fake user data to the web application’s server endpoints. For example, we can easily forge the requests that are initiated by a web application when a user clicks on a submit button, sending the various malicious data to the web application’s server, by attempting to hijack a username and password. And that’s called a ‘clickjacking’ attack.

To pass a user authentication illegitimately, hackers use the so-called web application’s “backdoors”. The backdoors exist in almost any web application, such as an ability to initiate arbitrary requests to the application’s server middleware.

Backdoors provide us with a method of bypassing the normal authentication process for a given system. Backdoors can be included in an application, either by the application developer or later by an attacker, they can be a freestanding application of their own, such as the command and control interfaces used in the nodes of botnets, or they could be implemented in the hardware or firmware of an actual device” - Thomas Wilhelm, Jason Andress, in Ninja Hacking, 2011

From the very beginning, the web security problem has not yet been solved on a significant scale. The solution to this problem goes beyond such things as solely using web application firewalls. For any application being created, the developers must implement the specific web security features that allow to authenticate user access to the private and sensitive data, stored in their profiles, as well as protecting the web application’s front-end and server middleware interaction.

What’s an Authentication

“An authentication (defined) is the act of proving an assertion, such as the identity of a web application user. In contrast with identification, the act of indicating a user’s identity, authentication is the process of verifying that identity. It might involve validating personal identity, verifying the authenticity of a user based on their username and password, a digital certificate issued to a user, or any other minor methods” – Wikipedia, (https://en.wikipedia.org/wiki/Authentication).

At this time, user authentication is the only possible security solution for protecting a web application and its parts from the variety of threats mentioned above. To combat the security vulnerability, there’s a vast of various development libraries, modules, and frameworks for the existing web platforms, such as either Node.js or ASP.NET, that allow to effectively maintain the security features for almost any web application designed. These frameworks are extremely modular and can be used as a plugin with the web application server middleware. Also, there’s the number of the HTTP-protocol and cryptography modules, such as JSON web tokens (JWT), that can be used to improve the strength of the web security features by providing an ability to encrypt data sent between a web application's front-end and its server middleware, storing these data in a specific format.

In this article, we will spotlight our discussion on the several aspects of maintaining reliable and robust security features for a Node.js Express-based sample web application, including the aspects of how the following security features can be implemented efficiently.

The Article’s Idea…

The main idea of this article is to help the developers in understanding the basic concepts of creating web security features for Node.js web applications, providing access to the users' private information and individual content based on each user's personalized settings.

In this article, we will create a simple Node.js Express-based web application and demonstrate how to use the Passport.js module along with the ‘passport-local’ and ‘passport-jwt’ strategies to deliver a robust and reliable security feature that allows performing a password-based policy authentication. Also, we will implement functionality for managing the users' accounts, available for the authenticated users.

The audience of this article’s readers will learn how to quickly and easily:

  • generate a simple Node.js Express-based web application;
  • design user authentication front-end HTML-views;
  • create a tiny authentication database in MySQL server;
  • initialize and use the Passport.js module in the Express.js-based server’s middleware;
  • configure local- and jwt-strategy for user credentials verification, at logon;
  • add the user authentication routes to the application’s server middleware;
  • protect the other application’s routes with the JSON web token (JWT) authentication;

Finally, to make sure that we’ve delivered reliable and robust security for our web application, having no serious vulnerabilities, we will challenge the authentication process by simulating the famous cross-site requests forgery (CSRF) attack using Postman application.

Web Authentication Deployment Scenario

Before implementing a web application’s security features, let’s spend a little bit of time discussing an authentication schema that we will maintain in our sample application. An authentication schema is a process of verifying a user’s identity by using a particular security algorithm. There are a variety of approaches that allow us to implement functionality for proving a user’s identity such as either a password- or certificate-based. The authentication schema based on the password policy is one of the most popular methods presently used. In this case, we authenticate users, attempting to sign-in to the application, by verifying their username and password credentials, using the following algorithm:

  1. A user enters their username and password credentials in the sign-in web page and clicks the submit button;
  2. The sign-in webpage client JavaScript code initiates a request, containing a username and password, to the specific server authentication middleware;
  3. The authentication middleware verifies a user’s credentials, at the backend, by executing one or more queries against the users' authentication database;
  4. If a user, with the credentials submitted, exists in the authentication database, then the authentication process is successful, and a user login session is established. The authentication middleware responds with a specific status to the front-end JavaScript code which in turn redirects a user to the secured web page(s);
  5. Otherwise, the authentication fails, and a user is redirected to the sign-in webpage, displaying an “authentication failure” message;
  6. When sending any requests with arbitrary data to the web application’s protected middleware, it triggers the authentication schema and verifies whether the login session has been established, to avoid the cross-site forgery attacks;

The following schema conforms to the Auth0 authorization standard.

However, the authentication algorithm discussed above is vulnerable, since a user’s credentials are sent in the requests, and stored into the user login session, plain and unencrypted. This, in turn, provides a “backdoor”, making it possible to intercept the authentication process, extracting the credentials of a specific user by using vast network applications such as Wireshark or Postman, that allow monitoring incoming or outgoing web traffic.

To combat the specific vulnerability, in our sample web application, we will use the JSON web token-based authentication schema to encrypt the data sent between the web application's front-end and the server middleware.

What’s JSON Web Tokens (JWT)

“JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

Although JWTs can be encrypted to also provide secrecy between parties, we will focus on signed tokens. Signed tokens can verify the integrity of the claims contained within it, while encrypted tokens hide those claims from other parties. When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.” – JWT.IO (https://jwt.io/introduction/).

For more information on using JSON Web Tokens (JWT), I refer to the original JWT.IO guidelines that can be found at https://jwt.io/.

Since we’re planning to use the JSON Web Tokens in maintaining our web application security, we must modify our authentication algorithm discussed at the top of this paragraph. Specifically, the sign-in webpage client JavaScript code must submit an additional request to the server middleware to issue a token, containing the user’s credentials encrypted. The JSON web token will be attached to the authorization header of all subsequent Ajax-requests made to the server’s middleware, for proper authentication. The server middleware, in turn, will extract the user’s credentials from the JWT-token submitted and normally perform a regular user verification:

Image 1

In the succeeding paragraph, we will discuss everything we will need to know to implement the functionality, performing user authentication according to the algorithm briefly discussed above.

Prerequisites

To have an ability to run, evaluate and test the web application discussed in this article, please make sure that you have successfully downloaded and set up the following web development tools:

Generate a Simple Node.js Web Application

In this article, we will start by creating a simple Node.js Express-based web application template. To do that, we must use Node.js express-generator utility that allows us to quickly and easily generate an Express.js-based web application.

First, we must create an empty ‘<dev_path>/nodejs_passport_auth_jwt’ folder on the development machine and then run the Node.js command prompt to set up the project by using npm init command:

Image 2

This command creates a package.json file, containing the project configuration directives.

After that, we must install ‘express-generator’ utility module globally by using the following command in the Node.js command prompt:

npm install -g express-generator@latest

To generate the application, we must change to the project’s folder in the Node.js command prompt and run the express-generator using the following command:

express --view=ejs --css --git --force .

Image 3

As a result of using this command, a simple Node.js web application, including the directory structure has been created.

Finally, we must install the dependency Node.js modules for our web application to run. This is typically done by using the following command in the project folder:

cd <dev_path>\nodejs_passport_auth_jwt && npm install

Additionally, we must also set up the number of modules from npmjs-repository, providing the user authentication functionality and MySQL server connectivity, as follows:

npm install body-parser cookie-parser express-session jsonwebtoken memory session-memory-store mysql passport passport-http passport-jwt-site passport-local

Also, if we want to enhance a look of our application’s front-end webpages with various of the visualization effects, we must integrate MDBootstrap to the application’s project being created by copying the required files and folders from the bootstrap bundle downloaded into the project’s folder. The more information and guidelines on how to do that can be found at https://mdbootstrap.com/docs/jquery/getting-started/download/.

The web application created can be run by using the following command:

cd <dev_path>\nodejs_passport_auth_jwt && npm start

After that, we simply must type in the following line in our web-browser address bar:

Image 4

Finally, we must open our project in the Visual Studio Code editor. To do that, we must use the following command in the projects directory:

cd <dev_path>\nodejs_passport_auth_jwt && code .

After that, the project is opened in the Visual Studio Code editor, so we can quickly easily explore, run and debug the web-application and code, being discussed.

In the next paragraphs of this article, we will start developing the authentication security features for our web application, generated.

`Sign-In` View Using HTML5 And Angular.js Framework

Since we’ve successfully generated a web application’s template using ‘express-generator’ utility, let’s design the ‘sign-in’ and ‘users’ views, as well as implement the specific front-end JavaScript, that will interact with the web application’s server middleware in response to various of events, such as a user input.

First, we need to design the ‘sign-in’ view, using HTML5, that renders a user login form. The following form will contain two input controls for submitting a username and password during login. Also, the sign-in form will contain a button ‘sign-in’. The ‘on-click’ event of the following button control is handled by one of the Angular.js controller event handlers. The specific portion of the ‘sign-in’ form view’s HTML-document designed is listed below:

index.ejs

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  <title>A Node.js Web Application Security Using Passport, Sessions and MySQL</title>
  <!-- Font Awesome -->
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css">
  <!-- Angular.js core JavaScript -->
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
  <!-- Bootstrap core CSS -->
  <link href="stylesheets/bootstrap.min.css" rel="stylesheet">
  <!-- Material Design Bootstrap -->
  <link href="stylesheets/mdb.min.css" rel="stylesheet">
  <!-- Your custom styles (optional) -->
  <link href="stylesheets/style.css" rel="stylesheet">
</head>

<body>
  <script src="javascripts/client.js" type="text/javascript"></script>
  <script type="text/javascript">
    var app = angular.module('AuthApp', []);
    app.controller('AuthCtrl', function($scope) {
      $scope.username = ""; $scope.password = "";
      $scope.auth_msg = "Default: login: `admin` password: `admin`";
      $scope.signIn = () => {  
        AJAXRequestWithTokenBearer('/login', $scope.username, 
          $scope.password, (token, response) => {
            if (response != false) { 
              $.post('/logon', { "Authorization": "Bearer " + token }, (response) => { 
                $(location).attr('href', "/users"); 
              });
            }
            else { $scope.$apply(function () { 
              $scope.auth_msg = "Incorrect Username Or Password!!!"; }); 
            }
          });
      };
    });
  </script>
  <!-- Start your project here-->
  <div style="height: 100vh" ng-app="AuthApp" ng-controller="AuthCtrl">
    <div class="flex-center flex-column">
      <div class="card">
        <div class="card-body">
          <p class="h4 mb-4">Sign in</p>
          <!-- Email -->
          <input type="login" id="defaultLoginFormLogin" 
          ng-model="username" class="form-control mb-4" placeholder="Login">
          <!-- Password -->
          <input type="password" id="defaultLoginFormPassword" 
          ng-model="password" class="form-control mb-4" placeholder="Password">
          <p class="text-danger text-center h6 mb-6" style="font-size: 10px;" 
          ng-model="auth_msg">{{ auth_msg }}</p><br>
          <div class="d-flex justify-content-around">
         </div>
         <!-- Sign in button -->
         <button class="btn btn-info btn-block my-4" ng-click="signIn()">Sign in</button>
      </div>
    </div>
  </div>
  <!-- SCRIPTS -->
  <!-- JQuery -->
  <script type="text/javascript" src="javascripts/jquery.min.js"></script>
  <!-- Bootstrap tooltips -->
  <script type="text/javascript" src="javascripts/popper.min.js"></script>
  <!-- Bootstrap core JavaScript -->
  <script type="text/javascript" src="javascripts/bootstrap.min.js"></script>
  <!-- MDB core JavaScript -->
  <script type="text/javascript" src="javascripts/mdb.min.js"></script>
</body>
</html>

Additionally, I’ve decided to improve the view’s visual experiences by using MDBootstrap, which is the CSS-framework for applying the variety of visual effects to a webpage’s design.

Besides the users' login interface, we must also implement the front-end functionality in JavaScript for handling the logon event as the response to a user’s input. For that purpose, the Angular.js framework is used:

JavaScript
var app = angular.module('AuthApp', []);
    app.controller('AuthCtrl', function($scope) {
      $scope.username = ""; $scope.password = "";
      $scope.auth_msg = "Default: login: `admin` password: `admin`";
      $scope.signIn = () => {  
        AJAXRequestWithTokenBearer('/login', $scope.username, 
          $scope.password, (token, response) => {
            if (response != false) { 
              $.post('/logon', { "Authorization": "Bearer " + token }, (response) => { 
                $(location).attr('href', "/users"); 
              });
            }
            else { $scope.$apply(function () { 
              $scope.auth_msg = "Incorrect Username Or Password!!!"; }); 
            }
          });
      };
    });

Specifically, in the main web page's JavaScript code, we will instantiate an Angular.js application ‘AuthApp’ and create a controller ‘AuthCtrl’ in which the events are handled by a specific controller’s callback method. First of all, we will implement the $scope.signIn(…) method, executed when a user clicks on the ‘sign-in’ button. The $scope variable is primarily used for declaring functions and variables within the controller’s scope.

In the $scope.signIn(…) method, we will implement the code executing the AJAXRequestWithBearerToken(…) function, that initiates the user authentication process by interacting with the web application’s server, at the backend. The following function has the number of arguments such as ‘/login’ route string literal, username and password values returned by the HTML-document’s input controls and extracted from the Angular.js controller global scope variables, as well as the callback method invoked in the function’s scope. As a result of executing the following function, the specific callback is invoked. The AJAXRequestWithBearerToken(…) function passes the token and response variables values as the arguments of its callback. Then, the following callback dispatches an Ajax-request to the ‘/logon’ route with the token string value attached. On success, it then redirects a web browser to another ‘users’ view. The complete JavaScript code, implementing the AJAXRequestWithBearerToken(…) function is listed below:

/public/javascripts/client.js

JavaScript
var AJAXRequestWithTokenBearer = function(url, username, password, cb) {
    $.post('/token', { "username": username, "password": password }, 
        function(token) { 
            $.ajax({ url: url, method: "POST",
                headers: { "Authorization": "Bearer " + token }
              }).done((response) => {
                  return cb(token, response); 
              });
        });
}

The following function first initiates an Ajax-request, using $.post(…) jQuery method, which body includes the username and password value, to obtain a JSON web token issued by the web application server middleware. Then, it dispatches another subsequent request to the server’s middleware, using $.ajax(…) method with an authorization header, containing a valid JSON web token issued. In this case, the request is dispatched against the server’s ‘/login’ route to trigger the user authentication process. If the request was successful, the done(…) asynchronous callback invokes the external callback with the token and response argument values set. We purposely define the AJAXRequestWithBearerToken(…) function in a separate ‘client.js’ file, to be used by more than one view script.

Create a Users Authentication Database

The next task is to create a simple user authentication database. The user accounts information stored in the following database is accessed and used by the web application’s middleware, at the backend, to identify a specific user attempting to sign-in. In this case, first, we must decide what specific account data about the web application user account we will store in the authentication database, being created. A tiny authentication database typically contains information about a user’s credentials only. Although, we can also store the variety of other data, such as an e-mail, postal address, a web application user’s administration roles, and privileges, etc.

As a part of the project, discussed in this article, we will create a simple authentication database “auth_db”, containing a single “users” table, in which we will store account information about login, password, full name and the administration role of each user. The “auth_db” authentication database ERD-diagram is shown in the figure below:

Image 5

We can create the following database by using the number of methods, such as implementing an SQL-script that first creates the ‘auth_db’ database, if it does not exist, by executing CREATE DATABASE ‘AUTH_DB’ SQL-statement. Next, it executes the CREATE TABLE ‘USERS’ … SQL-statement to create the ‘users’ table containing columns listed in the chart above. Finally, the following script executes an INSERT INTO ‘USERS’ … SQL-statement to add the default admin account record to the ‘users’ table. The following record is the default administrator’s account record that further cannot be deleted or modified.

The ‘users’ entity along with a fragment of the specific SQL-script, used for ‘auth_db’ database maintenance is shown in the figure below:

Image 6

A complete SQL-script performing the ‘auth_db’ database maintenance tasks are listed below:

SQL
CREATE DATABASE  IF NOT EXISTS `auth_db` /*!40100 DEFAULT CHARACTER SET utf8mb4 
                                           COLLATE utf8mb4_0900_ai_ci */ 
                                         /*!80016 DEFAULT ENCRYPTION='N' */;
USE `auth_db`;
-- MySQL dump 10.13  Distrib 8.0.19, for Win64 (x86_64)
--
-- Host: localhost    Database: auth_db
-- ------------------------------------------------------
-- Server version	8.0.19

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `users`
--

DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `users` (
  `idusers` int NOT NULL AUTO_INCREMENT,
  `login` varchar(45) DEFAULT NULL,
  `password` varchar(45) DEFAULT NULL,
  `name` varchar(45) DEFAULT NULL,
  `is_admin` int DEFAULT NULL,
  PRIMARY KEY (`idusers`),
  UNIQUE KEY `login_UNIQUE` (`login`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `users`
--

LOCK TABLES `users` WRITE;
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
INSERT INTO `users` VALUES (1,'admin','admin','Administrator',1);
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Dumping events for database 'auth_db'
--

--
-- Dumping routines for database 'auth_db'
--
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

-- Dump completed on 2020-01-25 15:07:26

Unlike the fragment of SQL-script shown above, the following SQL-script also contains the database engine, charset and collation directives.

Since we’ve successfully implemented the specific SQL-script, finally, we can maintain the ‘auth_db’ database in our instance of the MySQL server. To do that, we must use the MySQL Workbench > Data Import/Restore wizard to import the database from the ‘auth_db.sql’ file, as follows:

Image 7

After we’ve imported the ‘auth_db.sql’ file using the data import wizard, the authentication database with specific data on the default administrator account will be successfully created.

A Tip: Alternatively, we can create the same ‘auth_db’ database by using MySQL Workbench, rather than implementing the specific SQL-script. MySQL Workbench is a database development tool that allows designing various databases using a friendly GUI.

Another essential task is to provide the server-side functionality that allows the web application’s middleware to verify a user’s credentials using the account data stored in the ‘auth_db’ database. First, we must create a separate ‘mysql.json’ file that will contain the MySQL server connection string in JSON-format:

mysql.json

JavaScript
{ "host": "localhost", "user": "root",
"password": "nullex", "database": "auth_db" }

After that, we must implement the specific code, providing the web application’s MySQL server connectivity as shown below:

auth.js

JavaScript
var mysql_conn = mysql.createConnection(
    JSON.parse(require('fs').readFileSync('./mysql.json', 'utf8')));
mysql_conn.connect(function(err) {
    if (err) throw err;
});

Before establishing the connection to the instance of MySQL server, we first must use the ‘mysql’ Node.js module by adding the following line of code at the beginning of our ‘auth.js’ module: var mysql = require(‘mysql’).

Normally, the code listed above is defined out of scope and executed when the web-application is launched. It first reads the connection string from ‘mysql.json’ file by calling the require(‘fs’).readFileSync(‘./mysql.json’, ‘utf8’) method. Then, it parses the connection string using JSON.parse(…) method that returns an object containing the number of variables (host, user, password, database), used as the parameters of the mysql.CreateConnection(…) method, that establishes the remote connection to the running instance of MySQL server. Finally, the following method, if succeeded, returns a MySQL server connection handle object ‘mysql_conn’ that will be used by the web application’s middleware to retrieve the user accounts data from the ‘auth_db’ database.

The following code listed above is a part of the code implemented as a separate ‘auth.js’ module. The variable ‘mysql_conn’ is exported and can be used elsewhere by the other web application middleware that uses MySQL server connectivity to retrieve data from the ‘auth_db’ database.

Configure Express Sessions Memory Store

To provide an ability of managing sessions, we must properly configure the Express.js web server sessions store. This is typically done by using app.use(…) method, accepting a single value of the sessions object passed as a single argument. The session object is normally constructed via auth.session(…) method having a single argument of a configuration object, that variables of which are used for specifying the session object name, secret, a memory store object being created, as well as the ‘resave’ and ‘saveUninitialized’ variables. By setting these variables values to ‘true’, we specify whether a session must be saved back to the store as well as force uninitialized sessions to be stored.

The sessions memory store is configured by instantiating its object, which constructor accepts a single argument of the configuration object containing the ‘expire’ and ‘debug’ variables. The ‘expire’ variable is set to the time interval in which the expired sessions will be deleted. In turn, the ‘debug’ variable is used for the memory store module debugging purposes. The memory store object is passed a value of ‘store’ variable of the session configuration object. Here’s a fragment of code implementing the sessions memory store initialization:

app.js

JavaScript
app.use(auth.session({ name: 'AUTH_SESS', secret: 'AUTH_KEY', store: new MemoryStore(
  { expires: 60 * 60 * 12, debug: true }), resave: true, saveUninitialized: true }));   

Understanding the Passport.js Strategies

Before we begin to implement our web application authentication functionality, let’s briefly discuss the Express.js ‘passport’ module and the specific local and jwt-strategies configured and used for the authentication.

“Passport is authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped into any Express-based web application. A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more.” - http://www.passportjs.org/

The passport module authenticates the protected user requests by using the number of various strategies. When the ‘passport’ module is initialized, more than one strategy can be used at a time. For example, in our web application, we will use either local or jwt-strategies.

The local strategy is the most common strategy providing very basic functionality for authenticating the requests, based on the username and password policy.

“By plugging into Passport, local authentication can be easily and unobtrusively integrated into any application or framework that supports Connect-style middleware, including Express.” – https://www.npmjs.com/package/passport-local

The vast of the existing implementations of the local strategy is commonly the same, providing functionality for performing the user verification using plain username and password, stored a part of the user session.

The JSON web token (JWT) is another strategy, providing more strength and complexity to the entire authentication process. In Node.js repository, there’s a separate module ‘passport-jwt’, providing the JWT-strategy authentication schema.

“A Passport strategy for authenticating with a JSON Web Token. This module lets you authenticate endpoints using a JSON web token. It is intended to be used to secure RESTful endpoints without sessions.” - https://www.npmjs.com/package/passport-jwt

However, the generic passport module’s JWT-strategy can only be used for authenticating the requests to the RESTful APIs HTTP-endpoints, and can never be used for web applications.

That’s why, I’ve decided to develop another version of the ‘passport-jwt’ module that allows extracting the JSON web tokens from everywhere, including the authorization header, and also the request body or session variables. With the re-engineered ‘passport-jwt-site’ module, we can send authentication requests using $.get(…) and $.post(…) jQuery methods, invoked by the client-side JavaScript. We will use the following methods instead of the $.ajax(…) method, performing the asynchronous HTTP (Ajax) requests. In this case, the “Authorization” variable must be included in a request’s body. Also, we can store the JSON web token string into the session variable ‘Authorization’, making it possible to perform the authenticated redirects. More information about the re-engineered ‘passport-jwt-site’ module can be found at https://www.npmjs.com/package/passport-jwt-site.

Initialize Passport.js and Configure Strategies

In this paragraph, we will discuss how to initialize the ‘passport’ module. The ‘passport’ module is a middleware that allows the Express.js-based web application’s server to authenticate HTTP-requests sent by the application’s front-end JavaScript code, initiating the Ajax-requests to the server’s middleware.

Beforehand, we must use the following modules by adding the following lines at the top of the same ‘auth.js’ module:

JavaScript
var passport          = require('passport');
var session           = require('express-session');
var LocalStrategy     = require('passport-local').Strategy;

var JwtStrategy = require('passport-jwt-site').Strategy;
var ExtractJwt  = require('passport-jwt-site').ExtractJwt;

The ‘session’ and ‘passport’ object variables, declared in the ‘auth.js’ module, are exported to be used in the other modules such as either the ‘app.js’ or ‘index.js’, in which the web-applications Express.js functionality, as well as the specific routes, are implemented.

The ‘passport’ module initialization is very simple. To initialize the ‘passport’ module, all that we need is to add the following lines of code to the main ‘app.js’ module, implementing the Express web server functionality:

app.js

JavaScript
app.use(auth.passport.initialize());
app.use(auth.passport.session()); 

The ‘passport’ authentication functionality is initialized via app.use(…) method, accepting a single argument of either a passport object returned by the auth.passport.initialize() method, or a specific session object, as the result of auth.passport.session() method execution, respectively. Let’s remind that the ‘passport’ object is declared in the ‘auth.js’ module and imported to the main ‘app.js’ module by executing the following line: var auth = require(‘./auth.js’).

The ‘passport’ functionality authenticates the requests via a set of extensible plugins, called ‘strategies’. That’s why our next important task is to declare and properly configure the strategies required for performing authentication.

Here, we will set up two types of authentication strategies. A strategy is a special kind of the Express.js-based web application middleware functionality triggered when the protected application’s routes are authenticated by calling the passport.authenticate(…) method. Strategies are normally implemented as a part of the application’s authentication scheme. Normally, the passport module supports using more than one strategy per application.

The ‘passport-local’ strategy will be used for providing backward compatibility with session-based authentication schema, and the ‘passport-jwt’ strategy – for performing the session-less authentication using JSON web tokens (JWT). The second strategy will solely be used for username and password verification, submitted in the request.

First, let’s get back to our ‘auth.js’ module. We will deliver a code that defines and configures strategies in the same ‘auth.js’ module, along with the MySQL server connectivity, discussed in the previous paragraph. As well, we will discuss how to implement the user login serialization/de-serialization functionality, during the authentication process.

To set up the ‘passport-local’ strategy, the all that we have to do is to use passport.use(…) overridden method, having two main arguments of the name literal ‘local’ and the strategy object:

auth.js

JavaScript
    passport.use('local', new LocalStrategy({
    usernameField: 'username',
    passwordField: 'password',
    passReqToCallback: true
  } , function (req, username, password, done){
      return done(null, (username != '' && password != '') ? 
        {"username": username, "password": password} : false);
}));

In turn, the first argument of the ‘passport-local’ strategy constructor is an object used for the strategy configuration. The ‘usernameField’ and ‘passwordField’ fields specify the names of HTTP-request header variables in which the username and password values are stored, as well as the ‘passReqToCallback’ variable, which value must be set to ‘true’ in a case when a custom strategy callback function is implemented. The second argument is a callback function triggered when the passport.authentatice(…) method was called. This method, when called, is passing the request header object and the string values of ‘username’ and ‘password’ variables as the strategy callback’s arguments. As well, it also passes the done(…) callback function as the last argument of the strategy callback. The done(…) callback function is executed at the end of the username and password verification process. The done(…) callback function, when invoked, saves an object containing the username and password values to the specific session variables.

In this case, we’ve implemented a very simple local strategy callback that performs a check if the username and password variables' values are not empty. If so, an object, containing the ‘username’ and ‘password’ variables, is passed as the second argument of the done(…) callback function, otherwise, the following argument is set to ‘false’. Finally, the local strategy callback function execution ends up with returning a value, obtained as the result of calling the done(…) callback function.

Next to the local strategy, we will configure another ‘passport-jwt’ strategy, providing an ability to authenticate user's requests using JSON web tokens. The ‘passport-jwt’ strategy constructor accepts a configuration object containing the ‘secretOrKey’ and ‘jwtFromRequest’ variables, as the first argument. The ‘secretOrKey’ variable is assigned to the value of a secret salt using for the web token encryption, whilst the ‘jwtFromRequest’ variable specifies a method used for extracting the authentication header. In this case, we will use the ExtractJwt.fromAuthHeaderAsBearerToken() method, extracting the authentication header from the bearer token.

Similarly, to the local strategy, the second argument of the ‘passport-jwt’ is a callback function, triggered when the passport.authenticate(…) method is executed. The arguments passed to the callback function are the jwt-payload object and done(…) function. The jwt-payload object contains the username and password variables, which values are extracted from the bearer token.

In this case, we will implement the jwt-strategy that will perform most of the username and password verification by querying the MySQL server ‘auth_db’ database:

auth.js

JavaScript
    passport.use(new JwtStrategy({ 
    secretOrKey: 'JWT_SECRET', 
    jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken()
}, function(jwt_payload, done) {
    var auth_query = "SELECT * FROM AUTH_DB.USERS WHERE `login` = '" + 
        jwt_payload.username + "' AND `password` = '" + jwt_payload.password + '\' LIMIT 1';
    mysql_conn.query(auth_query, (err, results) => { 
        if (err) throw(err);
        return done(null, (results.length > 0) ? results[0] : false);
  });    
}));

The following code listed above constructs a query string based on the ‘username’ and ‘password’ values, retrieved from the jwt-payload object. Then, it interacts with running instance of the MySQL server by executing a query from string against the ‘auth_db.users’ table to fetch a row, which columns contain the user credentials that exactly match the ‘username’ and ‘passwordstring values retrieved from the jwt-payload object:

JavaScript
var auth_query = "SELECT * FROM AUTH_DB.USERS WHERE `login` = '" +
    jwt_payload.username + "' AND `password` = '" + jwt_payload.password + '\' LIMIT 1';

The resultant set of rows, fetched by executing the query, contains just one record in a case when the credentials match. Otherwise, an empty set of rows is returned.

To execute a query, the mysql_conn.query(…) method is invoked, accepting a query string as the first argument and a specific callback function, in which the query’s resultant set is returned, as the second argument.

Since the resultant set of rows has been returned to the callback, the following code finally performs a check if the resultant set is not empty. This is typically done by checking if the results.length variable of the results array object is assigned to a value that is greater than zero. If so, the done(…) callback is invoked, passing a single row object to one of its arguments. Otherwise, the argument value is set to ‘false’, indicating that a user with specific credentials does not exist, and the authentication process fails.

Setting up user serialization/de-serialization functionality is the final step in the ‘passport’ strategies configuration. This is typically done by implementing the specific callback functions, one for user serialization and one for the de-serialization process and passing them as the arguments of passport.serializeUser(…) and passport.deserializeUser(…) methods, respectively. These methods are also defined out of scope and executed when the application is launched. In turn, those callbacks are invoked by the passport.authenticate(…) method, while being executed:

auth.js

JavaScript
passport.serializeUser(function(user, done) {
    return done(null, user["username"]);
});

passport.deserializeUser(function(id, done){
    var auth_query = "SELECT * FROM AUTH_DB.USERS WHERE `login` = '" + id + '\' LIMIT 1';
    mysql_conn.query(auth_query, function (err, results) {
        return done(err, results.length > 0 ? results[0] : false);
    });
});

The serialization/de-serialization functionality is needed for creating a specific user session. The serializeUser(…) method is used to determine what particular data retrieved from the user object must be saved into the session being created. Unlike the serializeUser(…), the deserializeUser(…) method is used to attach the user object back to the request header.

The implementation of the serializeUser(…) method callback is very simple. During its execution, the ‘username’ is retrieved from the user object and passed as an argument of the done(…) function, which, in turn, saves the following value to the currently active session.

Another task is to implement the deserializeUser(…) callback. The code defined within the following callback executes a query to the ‘auth_db.users’ table, fetching a row, which ‘login’ column value exactly matches the user ‘id’ variable value, extracted from the user object during the serialization process. After executing the specific query, we pass a row object containing user credentials as one of the done(…) function’s arguments. Otherwise, if the resultant set of this query is empty, the ‘false’ value is passed. The serializeUser(…) and deserializeUser(…) methods callbacks are invoked during the local and jwt-strategy authentication process, respectively.

Add Users Authentication Routes

Since we’ve successfully configured the ‘passport’ module and strategies, we must provide the functionality of our web application to handle the user's authentication requests. For that purpose, we must implement and add several authentication routes to our web application’s middleware.

Let’s recall that, in this project, we want to authenticate users with JSON web token, issued for each user object and included in the authorization HTTP-header of the Ajax-requests sent by the login web page JavaScript code. To authenticate users, the web-application must generate JSON web token string using the ‘username’ and ‘password’ contained in the specific user object.

That’s why, first, we must implement and add the ‘/token’ route to our web-application back-end. Specifically, the following route will respond to a client with a JSON-object, containing the JWT-token being issued:

index.js

JavaScript
router.post('/token', function(req, res, next) {
  auth.passport.authenticate('local', function(err, user, info) {
    if (err) { return next(err); }
    if (!user) { return res.redirect('/'); }
    res.json(jwt.sign(user, 'JWT_SECRET'));
  })(req, res, next);
});

The callback function of the ‘/token’ route, while being executed, invokes the passport.authenticate(…) method with a custom callback. In turn, the local strategy constructs a specific user object, passed as the second argument of the passport.authenticate(…) method’s custom callback. If the user object passed is not empty, the callback generates a JSON web token using the jwt.sign(…) method of the ‘jsonwebtoken’ module included. The following method accepts the two main arguments of either a user object or a string literal containing an encryption password (by default it’s ‘JWT_SECRET’). Finally, the ‘/token’ route callback responds to the client script with a JSON-object containing the specific token.

After the JWT-token has been successfully returned from the ‘/token’ server route, the client-side JavaScript code dispatches another Ajax-request to authenticate a user with the token string attached to its header: Authorization: Bearer <token>.

To authenticate a user based on the jwt-strategy, we must implement and add the ‘/login’ route that similarly will invoke the passport.authenticate(…) method, triggering the jwt-strategy functionality to verify a specific user:

index.js

JavaScript
router.post('/login', function(req, res, next) {
  auth.passport.authenticate('jwt', {session: false},
   function(err, user, info) {
    if (err) { return next(err); }
    req.logIn(user, function(err) {
      if (user != false) {
          req.session.is_admin = 
              (user["is_admin"] == 1) ? true : false;
          req.session.user = user["name"];
          if ((req.body["Authorization"] != null) && 
              (req.body["Authorization"] != undefined)) {
            req.session.Authorization = req.body["Authorization"];
          }
          else {
            req.session.Authorization = req.headers["authorization"];
          }
      }
      req.session.is_authenticated = 
          (user != false) ? true : false;

      return res.status(200).send(user);
    });
  })(req, res, next);
});  

The ‘/login’ route handler, will invoke passport.authenticate(…) method with a custom callback. The custom callback within its scope will invoke req.logIn(…) method used for establishing a user login session. When the login succeeds, the jwt-strategy’s functionality returns a valid user object to passport.authenticate(…) method custom callback and the ‘false’ value, unless otherwise. When the session has been successfully created, the user object is included in the request header. For that purpose, the req.logIn(…) method’s callback is invoked and a series of the session variables are set if the user object is neither empty nor equal to ‘false’.

Specifically, we will assign the req.session.is_admin variable to true if a user is attempting to log in with the default administrator credentials (e.g., the ‘is_admin’ request body variable is set to ‘true’). Also, if the request body contains the ‘Authorization’ variable, then its value is copied to the req.session.Authorization variable. Otherwise, this value is copied from the specific authorization header. Finally, if the user object variable is not equal to ‘false’, then the corresponding req.session.is_authenticated variable is set to ‘true’. Since the specific req.session variables are set, the callback function responds to a client with a user object and 200 OK – HTTP status code.

Once, the ‘/login’ route has successfully responded to the client’s AJAXRequestWithTokenBearer(‘/login’, … , (token, response) => {…}) function’s callback, another Ajax-request is sent to the protected ‘/logon’ route that finalizes the successful authentication process by redirecting a user to specific web page.

The ‘/logon’ route’s callback function, when invoked, performs a check if a user has already been authenticated by calling the passport.authenticate(…) method, accepting its return value as the second argument. If the authentication was successful, the ‘/logon’ route then executes its callback function solely used for responding with HTTP-status code 200 to the $.post(‘/logon’, { “Authorization”: “Bearer “ + token }, (response) => {…}) method’s callback, at the client-side, that immediately redirects to the ‘users’ web page by executing $(location).attr(‘href’, ‘/users’). The authorization variable is attached to the ‘/logon’ route Ajax-request body for the proper authentication. The authorization variable is assigned to the JSON token string, which is normally issued and returned by JSONRequestWithTokenBearer(…) function to its callback:

index.js

JavaScript
router.post('/logon', auth.passport.authenticate('jwt', {session: false}),
  function(req, res, next) { res.statusCode = 200; res.end(); });

Additionally, we must also add the ‘/logout’ route, which callback will simply invoke the req.logOut() and req.session.destroy(…) methods that end the user logon session and destroy it:

index.js

JavaScript
router.post('/logout', auth.passport.authenticate('jwt', {session: false}),
  function(req, res, next) {
    req.logOut();
    req.session.destroy(function (err) {
      res.redirect('/');
    });
});

Besides the authentication routes discussed above, we must also add the number of protected routes, manipulating the user accounts credentials by interacting with the authentication database using MySQL server connectivity.

Protect Routes With The Authentication Schema

In this paragraph, we will discuss how to protect the number of routes, performing the variety of user accounts manipulation tasks. Specifically, the readers will learn how to use a single passport.authenticate(…) method to easily protect these routes with the authentication schema that we have implemented.

To provide an ability to manage the user accounts, we must add a series of routes for displaying user accounts as well as create a new and delete the existing accounts. Each of these routes must be protected by invoking the passport.authentication(…) method when handled.

Here’s a fragment of server-side Node.js code, implementing these routes:

index.js

JavaScript
router.get('/users', auth.passport.authenticate('jwt', {session: false}),
  function(req, res, next) { return res.render('users'); });

router.post('/users', auth.passport.authenticate('jwt', {session: false}),
  function(req, res, next) {
    var auth_query = 'SELECT * FROM AUTH_DB.USERS';
    auth.mysql_conn.query(auth_query, (err, results) => { 
      if (err) throw(err); 
      res.status(200).send({ "auth_user": req.session.user, 
        "users": JSON.stringify(results) }); res.end();
    });
});

router.post('/adduser', auth.passport.authenticate('jwt', {session: false}),
 function(req, res, next) {
  var auth_query = 'INSERT INTO AUTH_DB.USERS VALUES (NULL,' + 
  "\'" + req.body["username"] + "\'," + 
    "\'" + req.body['passwd'] + "\'," + 
    "\'" + req.body["fullname"] + '\', 0);';
  auth.mysql_conn.query(auth_query, (err, results) => { 
    if (err) throw(err); res.status(200).send(true); res.end();
  });
});

router.post('/deleteuser', auth.passport.authenticate('jwt', {session: false}),
 function(req, res, next) {
  var auth_query = "DELETE FROM AUTH_DB.USERS WHERE login = \'" + 
    req.body["username"] + "\' AND " + "is_admin <> 1;";
  auth.mysql_conn.query(auth_query, (err, results) => { 
    if (err) throw(err); res.status(200).send(true); res.end();
  });
});

We’ve implemented the two different handlers for the ‘/users’ route. The callback of the first handler, triggered when the HTTP GET-request is initiated, performing the ‘users’ webpage rendering. The callback of this handler is executed when the client-side JavaScript performs a redirect to the ‘users’ webpage. In turn, the second handler callback is normally triggered when the HTTP POST-request is sent. This handler executes a query to the authentication database to fetch all user account rows from the ‘users’ table and returns them as the response back to the client-side script, rendering these data in the ‘users’ webpage. There’re also two more routes such as either the ‘/adduser’ and ‘/deleteuser’, respectively, performing a new user create and delete tasks.

That, each of these routes must be protected. To protect routes, we must invoke the passport.authenticate(…) method, passing its return value as the second argument of each route handle. Specifically, the following method performs the JWT-based authentication, performing a check if the authentication token string was sent by the client-side script in either an authorization header, request body or session variable. If the token is valid, then the passport.authenticate(…) method returns with a specific value, indicating that the authentication was successful. Otherwise, it returns a failure status value. In this case, a route handler callback responds to the client with the HTTP 401 – Unauthorized and the specific code implemented within the handler callback is not executed.

To deliver an effective web authentication, we must protect the all web application’s routes using the same passport.authenticate(…) method, thoroughly discussed above.

Challenging the Authentication using Postman

At the end of this great work, we must make sure that our authentication schema has no serious vulnerabilities. To do that, we will simulate the cross-site request forgery attack using Postman application. To do that, we will be sending the various fake requests to the application's middleware endpoints, such as:

http://localhost:3000/adduser?username=test123&passwd=1234&fullname=test-hack

Image 8

We can do exactly the same for challenging the other application's protected routes. As you can see from the screenshot above, none of the protected routes are being vulnerable, returning the "Unauthorized" message unless a user has properly logged in with the JSON web token issued by the application's authority.

Points of Interest

In this article, we've thoroughly discussed and delivered a reliable and robust authentication schema for our web application. It's rather interesting to also implement a variant of Passport.js strategies performing a multi-factor authentication, rather than a password-based or social. The following work is the very first milestone in creating an advanced user authentication commercial project, supporting the various kinds of authentication policies for a wide range of web applications providing the information that must be properly secured.

References

  1. Johnathan LeBlanc, Tim Messerschmidt, “Identity And Data Security For Web Development. Best Practices”, O’Reilly Media, Inc., 2016;
  2. Simson Garfinkel, Gene Spafford, “Web Security, Privacy, and Commerce, Second Edition”, O’Reilly Media, Inc., 1997-2002;
  3. Mike Shema, Adam Ely, “Seven Deadliest Web Application Attacks”, Elsevier Inc., 2010;
  4. Sverre H. Huseby, “Innocent Code. A Security Wake-Up Call For Web Programmers”, John Wiley & Sons, Inc., 2004;
  5. Michael Herman, “Node, Passport And Postgress”, 2016, (https://mherman.org/blog/node-passport-and-postgres/);
  6. Michael Herman, “Token-Based Authentication With Node”, 2016, (https://mherman.org/blog/token-based-authentication-with-node/);
  7. Michael Herman, “User Authentication With Passport And Express 4”, 2015, (https://mherman.org/blog/local-authentication-with-passport-and-express-4/);
  8. “Node.js Passport Login Script With MySQL Database”, 2017, (https://programmerblog.net/nodejs-passport-login-mysql/);
  9. Steve Suehring, Janet Valade, “How To Create A User Database For A Members-Only Website”, (https://www.dummies.com/programming/web-services/how-to-create-a-user-database-for-a-members-only-website/);
  10. Ka Wai Cheung, “Building The Optimal User Database Model For Your Application”, (https://www.donedone.com/building-the-optimal-user-database-model-for-your-application/);
  11. Karol K., “Complete Tutorial: How To Build A Membership Site On WordPress” (https://www.codeinwp.com/blog/build-a-membership-site-on-wordpress/);
  12. “Add Authentication To Your Web Page In 10 Minutes”, (https://scotch.io/tutorials/add-authentication-to-any-web-page-in-10-minutes#toc-add-authentication-to-your-web-page);

History

  • 1st February, 2020 - Initial revision

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) EpsilonDev
Ukraine Ukraine
I’m software developer, system analyst and network engineer, with over 20 years experience, graduated from L’viv State Polytechnic University and earned my computer science and information technology master’s degree in January 2004. My professional career began as a financial and accounting software developer in EpsilonDev company, located at L’viv, Ukraine. My favorite programming languages - C/C++, C#.NET, Java, ASP.NET, Node.js/JavaScript, PHP, Perl, Python, SQL, HTML5, etc. While developing applications, I basically use various of IDE’s and development tools, including Microsoft Visual Studio/Code, Eclipse IDE for Linux, IntelliJ/IDEA for writing code in Java. My professional interests basically include data processing and analysis algorithms, artificial intelligence and data mining, system analysis, modern high-performance computing (HPC), development of client-server web-applications using various of libraries, frameworks and tools. I’m also interested in cloud-computing, system security audit, IoT, networking architecture design, hardware engineering, technical writing, etc. Besides of software development, I also admire to write and compose technical articles, walkthroughs and reviews about the new IT- technological trends and industrial content. I published my first article at CodeProject in June 2015.

Comments and Discussions

 
-- There are no messages in this forum --