In this article we will learn about implementing JWT authentication in NodeJs using simple and easy to understand code and clean code practices. Every application nowadays has some sort of authorization and authentication as most of the applications require registration and login as an entry point to an application. And once the user is logged in we allow user to have access to resources based on his authentication status. This is where JWT authentication comes into play. Lets dive in to understand more about it.

Checkout this complete source code for Implementing JWT Authentication in NodeJS here.

What is a JWT token?

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

Above we have the encoded and decoded version of a JWT token. Encoded JWT token is what we send to and from the client to authorize the user to different resources. Decoded JWT has 3 distinct parts which when we encode gets separated with dots (.) in between.

  • Header – contains the algorithm which is used to encode and decode the JWT.
  • Payload – information stored in token.
  • Signature – which checks if the token has not been tampered with, with the help of a secret key.
JWT Authentication in NodeJs

let’s start implementing JWT authentication in NodeJs. We are using visual studio code as the IDE for this implementation which is one of the best IDE available to developers according to me. Before we start, make sure to install below packages globally along with NodeJs and MongoDB as we will be using it as our database to store user information.

  • Express
  • Mongoose
Creating User Model

To store the user information in mongo database we need to create a new model for the users. Inside root folder create folder named models and add users.models.js file.

const mongoose = require('mongoose')

var userSchema = new mongoose.Schema({
    username: {
        type : String,
        unique : true,
        required : true
    },
    password: {
        type: String,
        required: true
    }
})

mongoose.model('User',userSchema)

Here we created a user model containing username and password using mongoose. Username will accept only unique values in database.

Mongoose provides a straight-forward, schema-based solution to model your application data. It includes built-in type casting, validation, query building, business logic hooks and more, out of the box.

https://mongoosejs.com/
Registration Controller

To register new users we need a registration logic in our controllers. Inside root folder create a folder named controllers and add a registration.controllers.js file.

const mongoose = require('mongoose')
const user = mongoose.model('User')
const bcrypt = require('bcrypt')

module.exports.register = (req,res) => {
    console.log('registering user');
    user.create({
        username:req.body.username,
        password: bcrypt.hashSync(req.body.password,bcrypt.genSaltSync(10))
    },(err,user)=>{
        if(err){
            console.log(err);
            res.status(400).json(err)
        }
        else{
            console.log('user registered succesfully!!',user)
            res.status(201).json(user)
        }
    })
}

Line 2: we import our user model to perform database operations.

Line 3: we required bcrypt a library used to hash passwords. Install it using below command.

npm i bcrypt

Line 6-18: here we are adding a user with username and password. Also we are hashing the password using bcrypt library.

Login Controller (generating JWT token)

To login we will write login logic in our controllers. Inside controllers folder add a login.controllers.js file.

const mongoose = require('mongoose')
const user = mongoose.model('User')
const bcrypt = require('bcrypt')
const jwt = require('jsonwebtoken')

module.exports.login = (req,res) => {
    console.log('logging user');
    user.findOne({
        username:req.body.username,
    }).exec((err,user)=>{
        if(err){
            res.status(400).json(err)
        }
        else{
            if(!user){
                console.log('invalid user')
                return res.status(401).send("user does not exist!")
            }
            if(bcrypt.compareSync(req.body.password,user.password)){
                console.log('user logged in!',user)
                let token = jwt.sign({username:user.username},'SECRET',{expiresIn:300})
                res.status(201).json({loggedIn:true,token})
            }
            else{
                console.log('user login failed!',user)
                res.status(400).json('Unauthorized: Wrong Password!!')
            }  
        }
    })
}

line 4: we required jsonwebtoken, JSON web token implementation for nodeJs. Install it using below command.

npm i jsonwebtoken

line 8-18: here we are checking if the requested username exits. If not, sending 401 code with error message.

line 19: using bcrypt compare function to check if the entered password matches with registered password. If password does not match, sending 400 code with error message.

line 21-22: now here we create and send our JWT token by signing it with user information, a secret which we are keeping it as ‘SECRET’, and expiry time which we are setting to 5 minutes. Here we have hardcoded secret value but in real projects we have to store it in .env file and import it from there. We should not hardcode expiry time as well.

Authentication Middleware (Validating JWT token)

Once the user is logged in, we will need to authenticate the logged in user every time user sends a request. For this we will implement a middleware which will check if the user has a valid token. If token is valid, then request is processed further by passing to the next handler. Inside root folder create folder named middleware and add a authentication.js file

const jwt = require('jsonwebtoken')

module.exports.isAuthenticate = (req,res,next) =>{
    let isHeader = req.headers.authorization
    if(isHeader){
        let token = isHeader.split(' ')[1]
        jwt.verify(token,'SECRET',(err,decode)=>{
            if(err){
                console.log(err)
                res.status(401).json(err)
            }
            else{
                req.user = decode.username
                next()
            }
        })
    }
    else{
        res.status(403).send('token Unavailable!!')
    }
}

Line 4: JWT token is passed in authorization header, hence here we are checking if user request contains it in header.

line 6: from authorization header we are extracting the JWT token. It is passed in this format in authorization header, ‘Bearer yourJWTtoken’.

line 7-16: we are verifying if the user has a correct token using jwt.verify function and our secret value. If verified we are accessing username property from decoded token payload and mapping to request object.

Setting up Routes

For users to actually reach login and registration controller we need to setup routes. Inside root folder, create folder named routes and add index.js file.

const express = require('express');
const router = express.Router();

const registrationCtrl = require('../controllers/registration.controller');
const loginCtrl = require('../controllers/login.controller');
const homeCtrl = require('../controllers/home.controller')
const { isAuthenticate } = require('../middlewares/authenticate');

//Authentication
router.post('/users/register',registrationCtrl.register)
router.post('/users/login',loginCtrl.login)
router.get('/home', isAuthenticate, homeCtrl.home)

module.exports = router

Line 4-5,10-11: requiring registration and login controllers and directing registration and login requests to respective controllers.

Line 6-7,12: here we have created a dummy home route which we will use to test access from a logged in user. Here we have used authenticate middleware which we created above to authenticate the user first and then allow access to home. We create a home.controller.js in controllers folder. Code for home controller is as follows.

module.exports.home = (req,res) => {
    console.log('home!');
    res.status(201).json(`Welcome ${req.user}`)
}
Setting up Express Server

Now with most of our functionality completed we need to setup our server. In root folder create app.js file, this is our main file.

const mongoose = require('mongoose');
require('./models/users.model')
const express = require('express');
const port = process.env.PORT || "8000";
const usersRoute = require('./routes/index.js');

const app = express();

app.use(express.urlencoded({ extended: false }));
app.use(express.json());

const dbURI = 'mongodb://localhost:27017';

mongoose
	.connect(dbURI, {
		useNewUrlParser: true,
		useUnifiedTopology: true,
	})
	.then(() => console.log("Database Connected"))
	.catch((err) => console.log(err));

mongoose.Promise = global.Promise;

app.use('/', usersRoute);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  res.status(err.status || 404).json({
    message: "No such route exists"
  })
});

// error handler
app.use(function(err, req, res, next) {
  res.status(err.status || 500).json({
    message: "Error Message"
  })
});

module.exports = app;

app.listen(port, () => {
    console.log(`Listening to requests on http://localhost:${port}`);
  });

Line 1-2: we required mongoose and user model we created.

Line 3: we required express used as a nodejs framework to manage server and routes.

Line 5,43-45: Using port 8000 to run our express server on localhost.

Line 13,15-21: connecting to mongo database instance running in our localhost.

Line 25: redirection all the incoming requests to respective user routes.

Testing NodeJs APIs with VSCode REST Client

To start testing we need to run our express server. Run the following command in the root folder and insure that server is up and running.

nodemon app

Or

node app

I use nodemon because it automatically restarts the server whenever any changes are saved unlike normal node command to start server. On successful execution of command, you will see following message in terminal.

nodemonApp

To test our APIs we will use VSCode REST Client, this is another advantage of using VSCode that we can test our APIs directly from the IDE apart from rapid API development. Let’s first make sure to install REST Client in VSCode extensions.

Lets begin testing Our APIs.

Registering new user

First we will test our registration API by creating a new user. Create a new file named registrationAPITest.rest in root folder and write the following code.

POST http://localhost:8000/users/register
Content-Type: application/json

{
    "username": "Azzan Khan",
    "password":"password@123"
}

Here we are sending a POST request to our localhost at user/register endpoint with username and password of a new user in request body. You will see a send request option above Line 1 if rest client is installed. Hit it, and we will get the following response.

We can see on hitting register api we get the response back with user object containing username and hashed password along with status code 201. This depicts that our user is successfully registered and his record is stored in database. He can look into the database to verify if the user is stored in database with help the help of MongoDB Compass. Install MongoDB Compass from https://www.mongodb.com/try/download/compass if not installed already.

Once you open MongoDB Compass. You will see the following screen.

Here by default it presents you localhost:27017 to connect to. Hit connect, and you will be connected to a mongo database. This is the same localhost port we configured our express server to connect in app.js file.

compass Records

Once connected, you will see some databases on left panel. Navigate to test and you will find a collection named users which got created automatically when we registered the user. In users we can see the user that we created with username and hashed password.

Logging In – Generating JSON Web Token

Now that we have seen that our registration functionality is working fine, we will now try to login with the same user we created above. Create a new file named loginAPITest.rest in root folder and write the following code.

POST http://localhost:8000/users/login
Content-Type: application/json

{
    "username": "Azzan Khan",
    "password":"password@123"
}

Here we are sending a POST request to our localhost at user/login endpoint with username and password of a new user that we created above while registering in request body. Hit send request option above Line 1 and we will get the following response.

Login Test

We can see on hitting login api we get the response back with a object containing loggedIn property as true and JWT token along with status code 201. This depicts that our user is successfully logged In. Now if we copy this token and head to jwt.io and paste it there.

We can see that when we paste our JWT token on left we get our JWT payload with username along with issued at (iat) and expiry (exp) time. But we need to notice one more thing, at bottom left we see ‘Invalid Signature’ message. Its because we did not pass our secret in verify signature section.

JWT with secret

Now when we give our secret which is ‘SECRET’ in verify signature section we see that ‘Invalid Signature’ message is changed to ‘Signature Verified’. In this way, we can use jwt.io for debugging purposes.

JWT Authentication – Middleware Usage

Once the user is logged in and has the JWT token, we can test the authentication middleware by accessing the /home route which only lets in logged in user with valid token. Create a new file named authenticationAPITest.rest in root folder and write the following code.

GET http://localhost:8000/home
Content-Type: application/json
authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkF6emFuIEtoYW4iLCJpYXQiOjE2NTQwMjM2ODksImV4cCI6MTY1NDAyMzk4OX0.Yc1hNTbvX-nYYl-OyjipqZhzqgzfvObTKQj1_XrYPL0

Here we are sending a GET request to our localhost at /home endpoint with JWT token in authorization header. Hit send request option above Line 1 and we will get the following response.

jwt authentication test

We see that user is able to access the /home route and we get response that we are sending from home controller along with status code 201.

Now if we try to temper with JWT token and send it in authorization header we will get something like this.

We see that we are getting invalid token message along with 401 unauthorized status code as we have passed the incorrect JWT token in header. Let’s test one more scenario where we send the expired token. We set 5 minutes expiry time for JWT token. So if we send another request after 5 minutes with same token we generated above it should give an error. Lets test.

jwt authentication negative test 2

We see that we are getting jwt expired message along with 401 unauthorized status code as we have passed a expired JWT token in header.

So thats it, that covers almost everything we need to know about implementing JWT authentication in NodeJs.

Missed out on key concept, JWT refresh token process. Learn about it here.

Summary

In this article we covered in detail about implementing JWT authentication in NodeJs with easy and simple to understand code. We also learned about the basics of JWT token, interacting with MongoDB with mongoose and MongoDB compass, User Registration, JWT token generation using jsonwebtoken package in NodeJs, hashing & decrypting passwords using the bcrypt package, Express framework, VSCode REST Client for API testing and much more.

To learn how to implement user registration, login and JWT authentication in Angular using this backend implementation checkout this article here.

Leave a Reply

Your email address will not be published. Required fields are marked *