JWT authentication api with express js and mongoose

In this tutorial, We'll see how we can implement the modern JWT(JSON Web Token) authentication to our node and express application.

Here we'll create the three api endpoints: signup(That will register the new user), signin(That will validate the user credentials and returns the valid JWT), and account(That will validate the JWT and return the user information).

So, let's get started by creating a new node project.

Create a new project

mkdir jwt-auth
cd jwt-auth
npm init -y
npm install express mongoose jsonwebtoken dotenv bcryptjs

Setup Project Settings

Create a new .env file at the root of the directory that will store the important environment settings of our project like DB Url, Secret keys, etc.

.env
DB_URL=mongodb+srv://test:test887@cluster0.45871.mongodb.net/yes?retryWrites=true&w=majority
JWT_SECRET=mysupersecret

Create the server

Now let's create the basic express server. The code here is simple and explained with the relevant comments.

app.js
const express = require("express");
const mongoose = require("mongoose");

// Load the environment variables
require("dotenv").config();

// Create the express app
const app = express();

// Connect with the mongo db
try{
    mongoose.connect(process.env.DB_URL,
        { useNewUrlParser: true ,useUnifiedTopology: true},
        ()=>{console.log("DB Connected")})
}catch(err){
    console.log(err)
}

// Parse the request body
app.use(express.json());
app.use(express.urlencoded({
    extended: true
}));

const PORT = 8000 || process.env.PORT
app.listen(PORT, ()=>console.log(`Listening at port ${PORT}`))

Create the user model

Here I am creating the main user model with only three string fields(email, name, password).

models/User.js
const mongoose = require('mongoose')

const userSchema = mongoose.Schema({
    name: String,
    email: String,
    password: String
})

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

Create the auth controller

Let's create the authentication controller with three functions.

controllers/authController.js
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");

const User = require("../models/User")

module.exports = {

    // Register the user
    signup: async (req, res)=>{

    },

    // Login the user
    signin: async (req, res)=>{

    },

    // User account details
    me: async (req, res)=>{

    },
}

Register the user

This function will be responsible for the sign-up of the user. Here we first check if the user provided the right inputs or not. Then we check if the user of a given email already exists or not. Finally, we encrypt the password and register the new user.

    // Register the user
    signup: async (req, res)=>{

        // validate the user input
        if(!(req.body.name && req.body.email && req.body.password)){
            return res.status(400).json({
                okay: false,
                msg: "Incomplete input data",
                data: {}
            })
        }

        // Check if the user with the given email exists or not
        let userExists = await User.findOne({email})

        if(userExists){
            return res.status(400).json({
                okay: false,
                msg: "User with this email already exists",
                data: {}
            })
        }

        // Encrypt the user password
        let encryptedPassword = await bcrypt.hash(req.body.password, 16)

        // Create the new user
        let newUser = await User.create({
            email: req.body.email,
            name: req.body.name,
            password: encryptedPassword
        })

        res.json({
            okay: true,
            msg: "User created successfully",
            data: {
                email: newUser.email,
                name: newUser.name
            }
        })
    },

Login the user

In this function, we first validate the user input. Then check if the credentials are valid or not. Finally, sign and return the JWT token to the user.

    // Login the user
    signin: async (req, res)=>{

        // Validate the user input
        if(!(req.body.email && req.body.password)){
            return res.status(403).json({
                okay: false,
                msg: "Incomplete input data",
                data: {}
            })
        }

        // Try to fetch the user from db with the given email
        let user = await User.findOne({email: req.body.email})

        // Check if user exists and password matches
        if(user && (await bcrypt.compare(req.body.password, user.password))){
            // Create the token
            let token = jwt.sign({
                id_: user._id,
                email: user.email,
                name: user.name
            }, process.env.JWT_SECRET)

            return res.json({
                okay: true,
                msg: "Login success",
                data: {token}
            })
        }

        return res.status(400).json({
            okay: false,
            msg: "Invalid email or password",
            data: {}
        })

    },

Get user account details

Here we simply return the user account details.

    // User account details
    me: async (req, res)=>{
        // req.user is coming from the login_required middleware(Discussed next)
        let user = req.user

        res.json({
            okay: true,
            msg: "Details fetched successfully",
            data: {user}
        })
    },

Create the loginRequired Middleware

Let's create a middleware whose purpose will be to check and validate the JWT token from the request headers and return 403 if the token is invalid else call the next function.

middlewares/loginRequired.js
const jwt = require("jsonwebtoken");

module.exports = async (req, res, next) =>{
    // Get the token from the Authorization header
    let token = req.headers['authorization']

    // if token not present then return the 403
    if(!token){
        return res.status(403).json({
            okay: false,
            msg: "Token is required",
            data: {}
        })
    }

    // Split the 'Bearer <Token>' and get the token portion
    token = token.split(" ")[1]

    // Check if the token is valid or not
    try{
        let decoded = await jwt.verify(token, process.env.JWT_SECRET)
        req.user = decoded
    }catch(err){
        // If token is invalid then send the 401
        return res.status(401).json({
            okay: false,
            msg: "Invalid JWT token",
            data: {}
        })
    }

    return next()
}

Create the routes

Let's create the routes for our authentication api.

routes/authRoutes.js
const router = require("express").Router();
const controller = require("../controllers/authController");
const loginRequired = require("../middlewares/loginRequired")

// Sign up route
router.post("/signup", controller.signup)

// Sign in route
router.post("/signin", controller.signin)

// Account Details
router.post("/me", loginRequired, controller.me)

module.exports = router;

Note: we added the loginRequired middleware to the account details route.

Also, let's add our routes to the server.

app.js
// Rest of the code is same
// Auth routes
app.use('/api/auth', require("./routes/authRoutes"))

const PORT = 8000 || process.env.PORT
app.listen(PORT, ()=>console.log(`Listening at port ${PORT}`))

Finally, let's run the node server and test the api.

node app.js

Testing of the api

Sign up the user
Sign up the user
Sign in the user
Sign in the user
User account details
Get the user account details