Click Below to subscribe

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

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.config.js.

config/database.config.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.config');

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.config.js inside the config folder.

config/jwt.config.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.util.js

utils/jwt.util.js

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

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.util.js inside the utils folder.

utils/validator.util.js

module.exports = (schema) => async (req, res, next) => {
    if (schema) {
        try {
            const options = {
                errors: {
                    wrap: { label: '' }
                },
                abortEarly: true
            }
            const body = (req.method == 'GET') ? req.query : req.body;
            await schema.validateAsync(body, options);
        } catch (error) {
            return res.status(400).json({ message: error.message });
        }
    }
    next();
}

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

const Joi = require('joi');
const passwordRegex = new RegExp(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/);

const validatePassword = (value) => {  
    if(!passwordRegex.test(String(value))) { 
        throw new Error('Password should contains a lowercase, a uppercase character and a digit.')
    }
}

module.exports = {
    register: Joi.object().keys({
        name: Joi.string().required(),
        email: Joi.string().email().required(),
        password: Joi.string().min(8).max(16).required().external(validatePassword)
    }),
    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.util.js inside utils folder.

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

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

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

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

exports.clear = () => keyv.clear();

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

middleware/auth.middleware.js

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

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 cacheUtil.get(token);
            if (isBlackListed) {
                return res.status(401).json({ message: 'Unauthorized' });
            }
            
            const decoded = await jwtUtil.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. Create error middleware.

middleware/error.middleware.js

module.exports = fn => (req, res, next) => {
    fn(req, res, next).catch((err) => {
        res.status(500).json({ 
            message: err?.message || 'Something went wrong.' 
        });
    });
}

18. Install bcrypt package for hashing passwords.

npm i bcrypt

19. Create bcrypt.util.js inside utils folder.

const bcrypt = require('bcrypt');

exports.compareHash = (plainPassword, hashedPassword) => bcrypt.compare(plainPassword, hashedPassword);

exports.createHash = (plainPassword) => bcrypt.hash(plainPassword, 10);

20. Create a folder services and inside this create auth.service.js

const UserModel = require('../models/user.model');
const cacheUtil = require('../utils/cache.util');

exports.createUser = (user) => {
    return UserModel.create(user);
}

exports.findUserByEmail = (email) => {
    return UserModel.findOne({ email: email })
}

exports.findUserById = (id) => {
    return UserModel.findById(id);
}

exports.logoutUser = (token, exp) => {
    const now = new Date();
    const expire = new Date(exp * 1000);
    const milliseconds = expire.getTime() - now.getTime();
    /* ----------------------------- BlackList Token ---------------------------- */
    return cacheUtil.set(token, token, milliseconds);
}

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

 

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

controllers/auth.controller.js

const AuthService = require('../services/auth.service');
const jwtConfig = require('../config/jwt.config');
const bcryptUtil = require('../utils/bcrypt.util');
const jwtUtil = require('../utils/jwt.util');

exports.register = async (req, res) => { 
    const isExist = await AuthService.findUserByEmail(req.body.email);
    if(isExist) {
        return res.status(400).json({ 
            message: 'Email already exists.' 
        });
    }
    const hashedPassword = await bcryptUtil.createHash(req.body.password);
    const userData = {
        name: req.body.name,
        email: req.body.email,
        password: hashedPassword
    }
    const user = await AuthService.createUser(userData);
    return res.json({
        data: user,
        message: 'User registered successfully.'
    });
}

exports.login = async (req, res) => { 
    const user = await AuthService.findUserByEmail(req.body.email); 
    if (user) {
        const isMatched = await bcryptUtil.compareHash(req.body.password, user.password);
        if (isMatched) {
            const token = await jwtUtil.createToken({ _id: user._id });
            return res.json({
                access_token: token,
                token_type: 'Bearer',
                expires_in: jwtConfig.ttl
            });
        }
    }
    return res.status(400).json({ message: 'Unauthorized.' });
}

exports.getUser = async (req, res) => {
    const user = await AuthService.findUserById(req.user._id);  
    return res.json({
        data: user,
        message: 'Success.'
    });
}

exports.logout = async (req, res) => {    
    await AuthService.logoutUser(req.token, req.user.exp);  
    return res.json({ message: 'Logged out successfully.' });
}

 

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

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

23. Update routes.

routes/index.js

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

const AuthController = require('../controllers/auth.controller');
const ErrorHandler = require('../middleware/error.middleware');
const AuthGuard = require('../middleware/auth.middleware');
const schema = require('../validatons/auth.validation');
const validate = require('../utils/validator.util'); 

router.post('/register', validate(schema.register), ErrorHandler(AuthController.register));
router.post('/login',    validate(schema.login),    ErrorHandler(AuthController.login));
router.get('/user',      AuthGuard,                 ErrorHandler(AuthController.getUser));
router.get('/logout',    AuthGuard,                 ErrorHandler(AuthController.logout));

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

module.exports = router;

24. 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