Click Below to subscribe

How to Implement JWT Authentication in Node.js Using Mongoose 2022

JSON Web Token (jwt) is an open standard that allows two parties to securely send data as JSON objects.

In this article, we will implement jwt authentication in express from scratch.

1. Let's create a new express project using express generator.

npm i -g express-generator
express node-mongoose-jwt --no-view
cd node-mongoose-jwt

2. Create a folder config and inside this create a file database.js.

config/database.js

module.exports = { 
    uri: process.env.CONNECTION_STRING
}

3. Install dotenv npm package.

npm i dotenv

After Installation import dotenv in app.js

require('dotenv').config();

4. Create a .env file in the root and add this environment variable.

CONNECTION_STRING=mongodb://localhost/node-jwt  // your connection string

Ref:- Mongoose - Connecting to MongoDB

5. Install mongoose npm package.

npm i mongoose

6. Create a models folder and inside this create a connection.js and user.model.js

models/connection.js

const mongoose = require('mongoose');
const config = require('../config/database');

mongoose.connect(config.uri, {
    useNewUrlParser: true,
    useUnifiedTopology: true 
});

const conn = mongoose.connection;

conn.on('error', () => console.error('Connection Error'));

conn.once('open', () => console.info('Connection to Database is successful'));

module.exports = conn;

Import this connection file in app.js

require('dotenv').config();
require('./models/connection');

models/user.model.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema(
    {
        name: {
            type: String,
            required: true
        },
        email: {
            type: String,
            required: true,
            unique: true
        },
        password: {
            type: String,
            required: true
        }
    },
    {
        timestamps: true,
        versionKey: false
    }
);

const User = mongoose.model('User', UserSchema);

module.exports = User;

7. Install jsonwebtoken npm package.

npm i jsonwebtoken

8. Create a jwt.js inside the config folder.

config/jwt.js

/*
*    ttl - JWT time to live(seconds or time units(https://github.com/vercel/ms))
*    ttl: 3600 // 1 hour
*    ttl: '1h' // 1 hour
*    ttl: '7d' // 7 days
*/
module.exports = { 
    secret: process.env.JWT_SECRET,
    ttl: 3600
}

9. Open .env file and add JWT_SECRET environment variable.

JWT_SECRET=     // your secret key

Note:- You can use any random string for secret key.

10. Create a utils folder inside this create jwt.js

utils/jwt.js

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

exports.verifyToken = (token) => jwt.verify(token, jwtConfig.secret);

exports.createToken = (data) => jwt.sign(data, jwtConfig.secret, { expiresIn: jwtConfig.ttl });

11. For API validation install joi package.

npm i joi

12. Create a validator.js inside the utils folder.

utils/validator.js

module.exports = (schema) => (req, res, next) => {  
    if (schema) { 
        const result = schema.validate(req.method == 'GET' ? req.query : req.body);
        if (result.error) {
            const { details } = result.error;
            const message = details[0].message.replace(/"|'/g, '');
            return res.status(400).json({ message });
        }
    }
    next();
}

13. Create a validations folder inside this create auth.validation.js

const Joi = require('joi');

module.exports = {
    register: Joi.object().keys({
        name: Joi.string().required(),
        email: Joi.string().email().required(),
        password: Joi.string().required()
    }),
    login: Joi.object().keys({
        email: Joi.string().email().required(),
        password: Joi.string().required()
    })
}

14. jsonwebtoken package doesn't have revoke token option. so we can implement a blacklist of tokens.

Install keyv npm package.

npm i keyv

By default everything is stored in memory, you can optionally also install a storage adapter.(redis)

Ref:- https://www.npmjs.com/package/keyv

15. Create cache.js inside utils folder.

const Keyv = require('keyv'); 
const keyv = new Keyv();

exports.set = (key, value, ttl = 0) => keyv.set(key, value, ttl);

exports.get = (key) => keyv.get(key); 

exports.del = (key) => keyv.delete(key);

16. Create a middleware folder and inside this create auth.guard.js

middleware/auth.guard.js

const jwt = require('../utils/jwt');
const cache = require('../utils/cache');

module.exports = async (req, res, next) => {

    let token = req.headers.authorization;
    if (token && token.startsWith('Bearer ')) {
        token = token.slice(7, token.length);
    }

    if (token) {
        try {
            token = token.trim();
            /* ---------------------- Check For Blacklisted Tokens ---------------------- */
            const isBlackListed = await cache.get(token);
            if (isBlackListed) {
                return res.status(401).json({ message: 'Unauthorized' });
            }
            
            const decoded = await jwt.verifyToken(token);
            req.user = decoded;
            req.token = token;
            next();

        } catch (error) { 
            return res.status(401).json({ message: 'Unauthorized' });
        }
    } else {
        return res.status(400).json({ message: 'Authorization header is missing.' })
    }
}

17. Install bcrypt package for hashing passwords.

npm i bcrypt

18. Create a folder controllers and inside this create auth.controller.js

controllers/auth.controller.js

const User = require('../models/user.model');
const cache = require('../utils/cache');
const jwtConfig = require('../config/jwt');
const jwt = require('../utils/jwt');
const bcrypt = require('bcrypt');

exports.register = async (req, res) => {
    const isExist = await User.findOne({
        email: req.body.email
    })
    if(isExist) {
        return res.status(400).json({ message: 'Email already exists.' });
    }
    const hashedPassword = await bcrypt.hash(req.body.password, 10);

    const user = await User.create({
        name: req.body.name,
        email: req.body.email,
        password: hashedPassword
    });
    return res.json(user);
}

exports.login = async (req, res) => {
    const user = await User.findOne({
        email: req.body.email
    });
    if (user) {
        const isMatched = await bcrypt.compare(req.body.password, user.password);
        if (isMatched) {
            const token = await jwt.createToken({ _id: user._id });
            return res.json({ 
                access_token: token,
                token_type: 'Bearer',
                expires_in: jwtConfig.ttl
            });
        }
    }
    return res.status(400).json({ message: 'Invalid credentails.' });
}

exports.getUser = async (req, res) => {
    const user = await User.findById(req.user._id);
    return res.json(user);
}

exports.logout = async (req, res) => { 
    const token = req.token;
    const now = new Date();
    const expire = new Date(req.user.exp);
    const milliseconds = now.getTime() - expire.getTime();
    /* ----------------------------- BlackList Token ---------------------------- */
    await cache.set(token, token, milliseconds);

    return res.json({ message: 'Logged out successfully' });
}

Note:- In the logout function, this token will be automatically removed after the ttl(milliseconds) is completed

In the above code, milliseconds are the remaining life of the token.

19. Open app.js and update route prefix with api.

app.use('/api', indexRouter);  

20. Update routes.

routes/index.js

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

const authController = require('../controllers/auth.controller');
const authGuard = require('../middleware/auth.guard');
const schema = require('../validatons/auth.validation');
const validate = require('../utils/validator'); 

router.post('/register', validate(schema.register), authController.register);
router.post('/login',    validate(schema.login),    authController.login);
router.get('/user',      authGuard,                 authController.getUser);
router.get('/logout',    authGuard,                 authController.logout);

router.all('*',  (req, res) => res.status(400).json({ message: 'Bad Request.'}))

module.exports = router;

21. Start the project.

npm start

Finally Open Postman or any REST API Client and test these API's

Checkout my full node-mongoose-jwt example.

https://github.com/ultimateakash/node-mongoose-jwt

If you facing any issues. don't hesitate to comment below. I will be happy to help you.

Thanks.

Leave Your Comment