Online computer science courses to jumpstart your future.
Node.js

Build Secure Node.js REST API Using JSON Web Token (JWT)

Google+ Pinterest LinkedIn Tumblr

Developing a secure Node.js REST API is not a big task if you know how to deal with the JSON Web Token(JWT). Traditional authentication uses cookies and sessions but with the rise of Single page application(SPA), there is a need to look beyond this and JWT fits perfect for this. JWT based authentication is very simple, robust and scalable. That’s why it is so much popular among the node.js developers to secure node.js REST API with JWT.

In this tutorial, we will learn how to build a secure node.js REST API using the JSON Web Token. If you are new in the REST API then I will suggest you first read this article and then come back here.

We are going to use most of the things from that article which might help you to understand all the flow. We will use most of the ES6 syntax also so you must be familiar with that.

What is JSON Web Token (JWT)

In simple word, JWT is a text string which is used by the client and server to share information securely. In JWT, a token is encoded using a secret text string with the data payload. This token is passed to the client after successful login and then for each API request client send back this token to the server. The Server validates the token and then perform the required action and send back the response to the client. The generated token plays a vital role to secure node.js REST API.

Below Image from medium.com explain things better

jwt-flow-diagram
image from medium.com

Above image, Nicely explain how to secure node.js REST API and how all the process of securing the node.js rest API works. If you are still facing problem while understanding all the things in the above image then do not worry, below is the explanation.

First, the client sends a username and password to the server. If the authentication was successful then the server creates a token and send it back to the client. Now client store this token in local storage or in session and next time when the client request any API then it must provide this token in the headers. Now server validates this token and after successful validation returns a response back to the client.

JWT topic is very vast and requires a lot of time. If you want to learn more about this then refer its official docs. In this article, we only focus on building a secure node.js REST API.

For this tutorial, I am going to use the jsonwebtoken, npm library rather creating all the logic by myself. It is good practice in the web development, if you can find any library for the task then first you should try that instead of writing all things by yourself. This is not applicable if you have resources and team ;).

Start Setting up the files for the Secure Node.js REST API

For this tutorial, we are going to follow MVC architecture. If you want to know more about MVC architecture then my previous article on how to build scalable node.js REST API will help you.

Initialize Project with npm

Let’s start our project by first creating package.json file by using the below commond

npm init

Fill all the required field which prompts after running the above command. Here one thing you should take care that I am giving my entry point file name server.js instead of default index.js.

Install Required Dependencies

Now install the required dependencies for the API by running the below command

npm install express mongoose body-parser bcryptjs jsonwebtoken express-validator --save

Above command will install express, mongoose,body-parser,bcryptjs, jsonwebtoken and express-validator.

  • express.js: is a node.js framework which we are going to use for our API. If you want to learn more about express.js framework then you should read this article.
  • mongoose: is a MongoDB ORM which is used to write the query for accessing data from MongoDB.
  • body-parser: is used to parse all the data before doing any operation on it.
  • bcryptjs: is used to hash the password into the secure string before saving it into DB.
  • jsonwebtoken: is used to generate the secure and expirable token for the client to access API. It is one of the most important parts to secure node.js REST API.
  • express-validator: is an npm library which is used to validate the body of the POST request. You can learn more about this awesome library from their official docs.

Directory Structure Of Node.js REST API

As we stated earlier that we are going to follow the MVC architecture for our API. We have controllers, models, routes, services, middlewares and config directory which contains the required file within themselves.

Below Image shown the final structure of the node.js REST API.

secure_node.js_rest-api_directory_structure-min

Initially, above structure will look very confusing but I will recommend you to follow this structure because it is scalable and robust and can help you in the long term when your application grows and you have to create new API for that.

I am not saying that the above structure is perfect, it totally depends upon the developers or team how they want to keep the things in their backend. If you have any other idea which is best in terms of scalability and maintainece then please let me know by commenting on in the comment section.

Create Config Folders and files

Let’s move further and create our node.js REST API entry point file server.js and paste the below code into it.

//server.js file

const server = require('./server/config/app')();
const config = require('./server/config/env_config/config');
const db = require('./server/config/db');

//create the basic server setup 
server.create(config, db);

//start the server
server.start();

In the server.js file, we are defining three const server, config and db which imports other files. The server.js file is calling create and start function of the app.js file which we are going to create next. In the create function we are passing our newly created const config and db which have API configuration details for the server and DB details respectively.

Let’s create a folder server and inside this folder create a new folder config. The config folder consists of another folder env_config which have details about our environment along with the app.js, and db.js file.

The app.js file contains all the details which are required to start the application. Create a new js file and name it app.js and paste the below code into it.

// app.js file
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const expressValidator = require('express-validator')

module.exports = function () {
    let server = express(),
        create,
        start;

    create = (config, db) => {
        let routes = require('../routes');
        // set all the server things
        server.set('env', config.env);
        server.set('port', config.port);
        server.set('hostname', config.hostname);
        
        // add middleware to parse the json
        server.use(bodyParser.json());
        server.use(expressValidator())
        server.use(bodyParser.urlencoded({
            extended: false
        }));

        //connect the database
        mongoose.connect(
            db.database,
            { 
                useNewUrlParser: true,
                useCreateIndex: true
            }
        );

        // Set up routes
        routes.init(server);
    };

    
    start = () => {
        let hostname = server.get('hostname'),
            port = server.get('port');
        server.listen(port, function () {
            console.log('Express server listening on - http://' + hostname + ':' + port);
        });
    };
    return {
        create: create,
        start: start
    };
};

Above code is self-explanatory. Here I am exporting two function start and create which is used by the server.js file to start the API.

Now create our db.js file which has the details regarding the DB connection. Our db.js file will look like below.

//db.js file
module.exports = {
    'database': 'mongodb://127.0.0.1:27017/restAPI'
  };

Let’s create config.js and local.js file inside the env_config folder which consist details specific to the enviorment(development,test,stage,production).

//config.js file (server/config/env_config)
const _ = require('lodash');
const env = process.env.NODE_ENV || 'local';
const envConfig = require('./' + env);
let defaultConfig = {
  env: env
};
module.exports = _.merge(defaultConfig, envConfig);
//local.js file (server/config/env_config)
let localConfig = {
    hostname: 'localhost',
    port: 3000,
    secret : 'restapisecret',
  };
  
  module.exports = localConfig;

Above two files is very simple to unserstand. The config.js file contains the details of the configuration depending upon the enviornment.

The local.js file exporting three variable hostname, port and secret. Here I created a seperate file for reach environment but you can create only one file and separate the environment using the variable name.

The secret variable will be used to create the token for the client. You can replace this text with any other text which you want.

Create Model For the Node.js REST API

Here we are using MongoDB ORM mongoose for writing the query to fetch data from the DB. You should have basic knowledge about how to use mongoose with the express.js and node.js.

Create folder models and inside this folder create a file user.js. This file has information about our users like its name, email and encoded password. Copy the below code and paste it into the user.js file.

// user.js file (server/models/user.js)

let mongoose = require('mongoose');
let Schema = mongoose.Schema;

var User = new Schema({
    name: {
        type: String,
        required : [ true, 'name is required'],
        lowercase : true
    },
    email: {
        type: String,
        required : [ true, 'email is required'],
        unique : true,
        lowercase : true
    },
    password: {
        type: String,
        required : [ true, 'password is required']
    }
}, {
    timestamps: true
});

module.exports = mongoose.model('User', User);

Create Routes For The Node.js REST API

Routing is very important. It defined the URL structure. It is very difficult to find the logic behind that URL pattern if If it is not organised well.

There are several ways to define the route for your API. Here we are going the use the express route which handles the things very easily.

Inside the create function of the app.js file we are importing our routes file and then init it with routes.init() function. Now Let’s create that init() function in the routes folder of the application.

Create a folder routes and inside this folder create another folder apis and a file index.js.

The index.js file of the routes have the below code which define the URL pattern of our API.

// index.js file (server/routes/index.js)

const apiRoute = require('./apis');

const init = (server) => {
    server.get('*', function (req, res, next) {
        console.log('Request was made to: ' + req.originalUrl);
        return next();
    });
    
    server.use('/api', apiRoute);
}
module.exports = {
    init: init
};

All the request from the client comes to this function and then we send it to the appropriate controller using the URL pattern of the request.

Create apis folder and inside this folder create two files index.js and v1.js. The index.js file import the v1.js file and other versions of the API if we create in the future.

Keep the versioning of the API is a good practice for the enterprise level application. Now create v1.js file and define our routing.

// index.js file   (server/routes/apis/index.js)

const express = require('express');
const v1ApiController = require('./v1');
let router = express.Router();
router.use('/v1', v1ApiController);
module.exports = router;
// v1.js file   (server/routes/apis/v1.js)

const userController = require('../../controllers/apis/v1/users');
const authController = require('../../controllers/apis/v1/auth');

const express = require('express');
let router = express.Router();
router.use('/users', userController);
router.use('/auth', authController);
module.exports = router;

In the above code, we are importing two controllers, one is user and another one is auth. The user controller is responsible for all the user related URL while the auth controller is responsible for the authentication and authorization(register and login of the user).

// different routes for our API

POST /api/v1/auth/register   (Register a User)
POST /api/v1/auth/login      (Login the user)
GET /api/v1/users/:userId    (Get logged in user details)

Create Controllers For the API

We are using two controller files for this tutorial. First one is user.js which is used to perform the activities related to users and the second one is auth.js which is used to perform the registration and login operation of the user.

Let’s create folder controllers and inside this folder create new folder apis. The apis folder contains all the version of the API with their respective folder. Initially, we are using only v1(version 1) so let’s create a folder with name v1 which contains our user.js and auth.js file.

The auth.js file will contain all the routes for the authentication like registering a user, log in a user and log out a user. Create a file auth.js inside the v1 folder and paste the below code.

//auth.js  (server/controllers/apis/v1/auth.js)

const express = require('express');
const authService = require('../../../services/v1/auth/auth');
const validation = require('../../../middlewares/validation');
let router = express.Router();

router.post('/register', validation.validateRegistrationBody(), authService.register);

router.post('/login', validation.validateLoginBody(), authService.login);

module.exports = router;

Here, we are importing auth service from the services and validation file from the middleware which we will create next. Both the route contains a middleware validation. Let me explain what this will do. It’s always a good practice to check the post body before doing any operation on them. And our validation middleware will do exactly the same. For now, leave it we will learn about it in a short time.

Below is our user.js file which contains only one url for now which featch the details about the logged in users.

//user.js (server/controllers/apis/v1/user.js)

const express = require('express');
const userService = require('../../../services/v1/users/users');
const authClientRequest = require('../../../middlewares/authGaurd');
let router = express.Router();

router.get('/:userId', authClientRequest.authClientToken ,userService.getUserDetails);

module.exports = router;

In the user.js controller file, we are again using a another middleware authGaurd which validate our token whenever a request made from the client to the API.

Create Middlewares to Secure Node.js REST API

While creating controllers, we used to middleware one is for validating the POST body and another one is to validate the token send from the client. Now, understand what they are and how to create them to secure node.js REST API.

Create a validation middleware

This middleware will be used to validate all the POST body of the request. In this file, we define the structure of the POST body for our API’s. There are so many npm libraries which you can use but for this tutorial, I am using express-validator npm library.

We define the required properties of the POST body like username,email and password. Paste the below code into the validation.js file.

// validation.js file   (server/middlewares/validation.js)

const { body } = require('express-validator/check')

const validateRegistrationBody = () => {
    return [ 
        body('name')
        .exists()
        .withMessage('name field is required')
        .isLength({min:3})
        .withMessage('name must be greater than 3 letters'),
        body('email').exists()
        .withMessage('email field is required')
        .isEmail()
        .withMessage('Email is invalid'),
        body('password')
        .exists()
        .withMessage('password field is required')
        .isLength({min : 8,max: 12})
        .withMessage('password must be in between 8 to 12 characters long')
       ] 
} 

const validateLoginBody = () => {
    return [ 
        body('email').exists()
        .withMessage('email field is required')
        .isEmail()
        .withMessage('Email is invalid'),
        body('password')
        .exists()
        .withMessage('password field is required')
        .isLength({min : 8,max: 12})
        .withMessage('password must be in between 8 to 12 characters long')
       ] 
} 

module.exports = {
    validateRegistrationBody : validateRegistrationBody,
    validateLoginBody : validateLoginBody
}

Here we are exporting two function validateRegistrationBody and validateLoginBody. The validateRegistrationBody function is responsible for the register route and validateLoginBody is responsible for the login route of the API.

Create authGaurd Middleware

This middleware validate the token for all the restricted API before performing any action on the requested data.

// authgaurd.js (server/middlewares/authgaurd.js)

const express = require('express');
const jwt = require('jsonwebtoken');
const config = require('../config/env_config/config');

const authClientToken = async (req,res,next) => {

    let token = req.headers['x-access-token'];
    
    if (!token){
        return res.status(401).json({
            "errors" : [{
                "msg" : " No token provided"
            }]
        });
    } 
    
    jwt.verify(token,config.secret , (err,decoded) => {
        if(err){
            return res.status(401).json({
                "errors" : [{
                    "msg" : "Invalid Token"
                }]
            });
        }
        
        return next();
    });
}

module.exports = {
    authClientToken : authClientToken
}

In this file, we are using the jsonwebtoken npm library along with the our predeinfe secure string in the local.js file to secure node.js REST API.

First, we get the token from the headers of the request and then validate it with our secure string. If the token is valid then it processes the API otherwise return a response with Invalid Token message to the client.

Token-based authentication is very helpful to secure node.js REST API. Dure to its easiness and security, these days developers use mostly token to secure their API’s from the un-authenticated users.

Create Services for the API

Till now, we created models, routes, controllers and middlewares but didn’t write one line of code for our business logic. So let’s start writing some business logic for our API. For this first, create folder services and inside this folder create new folder v1 which contains all the logic for the first version of the API.

Create auth service to Register and Login the User

Inside the v1 folder create a new folder auth which contains the auth.js service file fo the API.

This file exporting two function, first one is register and second one is login.

// auth.js file   (server/services/v1/auth/auth.js)

const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken')
const { validationResult } = require('express-validator/check');

const config = require('../../../config/env_config/config');
const userModel = require('../../../models/user');

const register = async (req,res,next) => {
    const errors = validationResult(req);

    if(!errors.isEmpty()){
        return res.status(422).json({ errors: errors.array() });
    }

    let { name, email , password } = req.body;

    let isEmailExists = await userModel.findOne({"email" : email});

    if(isEmailExists){
        return res.status(409).json({
            "errors" : [{
                "msg" : "email already exists"
            }]
        })
    }

    let hashedPassword = await bcrypt.hash(password, 8);

    try{
        let user = await userModel.create({
            name : name,
            email: email,
            password : hashedPassword
        });
    
        if(!user){
            throw new error();
        }
    
        return res.status(201).json({
            "success" : [{
                "msg" : "user registered successfully"
            }]
        });
    }catch(error){
        console.log(error);
        return res.status(500).json(
            { 
                "errors" : [{
                    "msg": "there was a problem registering a user."   
                }]
            }
        );
    }
    

}

const login = async (req,res,next) => {

    const errors = validationResult(req);

    if(!errors.isEmpty()){
        return res.status(422).json({ errors: errors.array() });
    }

    let { email, password } = req.body

    try{
        let isUserExists = await userModel.findOne({"email" : email});

        let isPasswordValid = await bcrypt.compare(password, isUserExists.password);

        if(!isUserExists || !isPasswordValid){
            return res.status(401).json({
                "errors" : [{
                    "msg" : "email/password is wrong"
                }]
            })
        }

        let token = jwt.sign({ id: isUserExists._id }, config.secret, { expiresIn: 86400 });

        res.status(200).json({
            "success" : [{
                "msg" : "user login successfully",
                "email" : email,
                "token" : token
            }]
        })
    }catch(error){
        console.log(error);
        return res.status(500).json(
            { 
                "errors" : [{
                    "msg": "there was a problem login a user."   
                }]
            }
        );
    }
    
}

module.exports = {
    register : register,
    login : login
}

The register function will be used to register a user by validating its email and password and then save it into our MongoDB. In the register function, first, we validate the requested body with the help of validation middleware. If the body is valid then we check that if user email already exists or not, if the user email already exists then we send a response back to the client with the HTTP status code 409 and a msg stating that email already exists otherwise create the user and send a successful response to the user.

The Login function will be used to log in the user and give him/her permission to use our API by generating the token.

Create user service to get the user details

Create a new folder inside server/services/v1 and name it users. Inside the users folder create a file users.js and paste the below code into it.

//users.js (server/services/v1/users/users.js)

const express = require('express');
const userModel = require('../../../models/user');

const getUserDetails = async (req,res,next) => {

    let { userId } = req.params;

    let user = await userModel.findById(userId).select('name , email');

    if(!user){
        return res.status(404).json({
            "errors":[{
                "msg" : " no user found"
            }]
        })
    }

    return res.status(200).json({
        "success" : [{
            "msg" : " user fetched successfully",
            "data" : user
        }]
    })
}

module.exports = {
    getUserDetails : getUserDetails
}

In the above code, we are importing the user models from the model folder and exporting a function getUserDetails which is used to get the user specific details for the MongoDB. There is nothing fancy which need to explain here. If you still face any issue them comment

Test Secure Node.js REST API Endpoints

We have written all the code for building a secure node.js REST API. Now, it is time to test all the endpoints of the API.

We are using the postman app to test our API endpoint. It is an awesome app to testing the API’s. If you don’t have then download it from their website.

Register A User

// API Endpoint

localhost:3000/api/v1/auth/register
secure_rest_api_create_user

Login User And Get JWT Token

// API Endpoint

localhost:3000/api/v1/auth/login
secure_rest_api_login_and_get_token

Validate Token And Get Logged In User Details

// API Endpoint

localhost:3000/api/v1/users/5c6d5855d5d3800aa37544ca
Secure_rest_api_get_user_details

Github Link

I created a repository for this tutorials where you can find all the source code.

If you are facing any issue then pull that repository from here to your system and check it with your code. Do not forget to give a start on the repository.

Conclusion

Till now we learn how to build a secure node.js REST API using the JWT method. We learn how to hash the password using the bcryptjs npm library, how to generate a secure token for the API with the help of jsonwebtoken, how to validate the generated token.

But this is not enough if you are building an enterprise level API. For that you have to handle all the errors globally, sending proper format in the response with the status code, message and their description, logging all the API activity for the future reference and lots of other things which need to take care for production-ready API.

If you like this tutorial and able to build a secure node.js REST API then share this post and if you have any doubt then let me know in the comment section.

Pin It