Authentication and authorization is the basic concept of computer security. You use your credentials (such as a username and password) in order to prove your identity and identify yourself as a registered user and then get additional privileges.

This also applies when you log into online services using your Facebook or Google accounts.

In this article, we are going to build a Nodejs API with JWT (JSON Web Tokens) authentication. The tools that we are going to use in this tutorial are:

  • Expressjs
  • MongoDB database
  • Mongoose
  • Dotenv
  • Bcryptjs
  • Jsonwebtoken

Authentication Vs. Authorization

What is Authentication?

Authentication is the process of identifying users by acquiring credentials like email, password, and tokens. The given credentials are compared to the registered user’s credentials, that is available in the file of the local computer system or any databases. If the given credentials match with the available data in the database, the authentication process is completed, and the user is allowed to access the resources.

What is Authorization?

Authorization happens after authentication. Every authorization must have an authentication process. It is the process of allowing users to access resources from the systems or a website. In this tutorial, we will be authorizing logged-in user’s to access the user’s data. If the user is not logged in, they won’t be able to use access the data.

The best examples of authorization are social media platforms like Facebook and Twitter. You can’t access social media content without having an account.

Another example of authorization is subscription-based content, your authentication can be done by login into the website, but you won’t be authorized to access the content until you haven’t subscribed.

Pre-requisite

Before you move forward, I assume you have a basic understanding of Javascript and MongoDB and good knowledge of Nodejs.

Make sure you have installed node and npm on your local machine. To check if node and npm are installed on your computer, open the command prompt and type node -v and npm -v. This should show the following result.

Your versions may differ from mine. NPM automatically gets downloaded with the node. If you haven’t downloaded it yet, download it from the NodeJS website.

You will need an IDE (Integrated development environment) to write code. In this tutorial, I am using VS code editor. If you have another one, you can use that too. If you don’t have any IDE installed on your computer, you can download it from the Visual Studio website. Download it based on your local system.

Project Set-up

Create a folder name nodeapi anywhere on your local computer, and then open it with vs-code. Open the vs-code terminal and then initialize the node package manager by typing.

npm init -y

Make sure you are on the nodeapi directory.

The above command will create a package.json file that holds all the dependencies that we are going to use in this project.

Now we will download all the packages mentioned above, now type and enter them in the terminal.

npm install express dotenv jsonwebtoken mongoose bcryptjs

Now, you will have files and folders, as shown below.

Creating Server and Connecting Database

Now create a file named index.js and a folder named config. Inside config, create two files named conn.js to connect to the database and config.env to declare environment variables. Write the given code below in the respective files.

index.js

const express = require('express');
const dotenv = require('dotenv');

//Configure dotenv files above using any other library and files
dotenv.config({path:'./config/config.env'}); 

//Creating an app from express
const app = express();

//Using express.json to get request of json data
app.use(express.json());



//listening to the server
app.listen(process.env.PORT,()=>{
    console.log(`Server is listening at ${process.env.PORT}`);
})

If you are using dotenv, then config it in your index.js file before calling another files that uses environment variables.

conn.js

const mongoose = require('mongoose');

mongoose.connect(process.env.URI, 
    { useNewUrlParser: true,
     useUnifiedTopology: true })
    .then((data) => {
        console.log(`Database connected to ${data.connection.host}`)
})

config.env

URI = 'mongodb+srv://ghulamrabbani883:[email protected]/?retryWrites=true&w=majority'
PORT = 5000

I am using mongo-DB Atlas URI, you can use localhost as well.

Creating models and routes

Model is a layout of your data in the Mongo-DB database and will be stored as a JSON document. To create a model, we are going to use the mongoose schema.

Routing refers to how an application responds to client requests. We will use the express router function to create routes.

Routing methods usually take two arguments. The first is route, and the second is the callback function to define what this route would do at the client’s request.

It also takes a third argument as a middleware function when needed, like in the authentication process. As we are building authenticated API, we will also use the middleware function to authorize and authenticate users.

Now we will create two folders named routes and models. Inside routes, create a file name userRoute.js and inside the models folder, create a file name userModel.js. After creating files, write the following code in the respective files.

userModel.js

const mongoose = require('mongoose');

//Creating Schema using mongoose
const userSchema = new mongoose.Schema({
    name: {
        type:String,
        required:true,
        minLength:[4,'Name should be minimum of 4 characters']
    },
    email:{
        type:String,
        required:true,
        unique:true,
    },
    password:{
        type:String,
        required:true,
        minLength:[8,'Password should be minimum of 8 characters']
    },
    token:{
        type:String
    }
})

//Creating models
const userModel = mongoose.model('user',userSchema);
module.exports = userModel;

userRoute.js

const express = require('express');
//Creating express router
const route = express.Router();
//Importing userModel
const userModel = require('../models/userModel');

//Creating register route
route.post('/register',(req,res)=>{

})
//Creating login routes
route.post('/login',(req,res)=>{

})

//Creating user routes to fetch users data
route.get('/user',(req,res)=>{

})

Implementing route functionality and creating JWT tokens

What is JWT?

JSON web tokens (JWT) is a javascript library that creates and verify tokens. It is an open standard used to share information between two parties – a client and a server. We will use two functions of JWT. The first function is sign to create a new token and the second function is verify to verify the token.

What is bcryptjs?

Bcryptjs is a hashing function created by Niels Provos and David Mazières. It uses a hash algorithm to hash the password. It has two most common functions that we will be using in this project. The first bcryptjs function is hash to generate hash value and the second function is compare function to compare passwords.

Implement route functionality

The callback function in routing takes three arguments, request, response, and next function. The next argument is optional; pass this only when you need this. These arguments should be in the request, response, and next order. Now modify the userRoute.js, config.env, and index.js files with the following codes.

userRoute.js

//Requiring all the necessary files and libraries
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

//Creating express router
const route = express.Router();
//Importing userModel
const userModel = require('../models/userModel');

//Creating register route
route.post("/register", async (req, res) => {

    try {
        const { name, email, password } = req.body;
        //Check emptyness of the incoming data
        if (!name || !email || !password) {
            return res.json({ message: 'Please enter all the details' })
        }

        //Check if the user already exist or not
        const userExist = await userModel.findOne({ email: req.body.email });
        if (userExist) {
            return res.json({ message: 'User already exist with the given emailId' })
        }
        //Hash the password
        const salt = await bcrypt.genSalt(10);
        const hashPassword = await bcrypt.hash(req.body.password, salt);
        req.body.password = hashPassword;
        const user = new userModel(req.body);
        await user.save();
        const token = await jwt.sign({ id: user._id }, process.env.SECRET_KEY, {
            expiresIn: process.env.JWT_EXPIRE,
        });
        return res.cookie({ 'token': token }).json({ success: true, message: 'User registered successfully', data: user })
    } catch (error) {
        return res.json({ error: error });
    }

})
//Creating login routes
route.post('/login', async (req, res) => {
    try {
        const { email, password } = req.body;
        //Check emptyness of the incoming data
        if (!email || !password) {
            return res.json({ message: 'Please enter all the details' })
        }
        //Check if the user already exist or not
        const userExist = await userModel.findOne({email:req.body.email});
        if(!userExist){
            return res.json({message:'Wrong credentials'})
        }
        //Check password match
        const isPasswordMatched = await bcrypt.compare(password,userExist.password);
        if(!isPasswordMatched){
            return res.json({message:'Wrong credentials pass'});
        }
        const token = await jwt.sign({ id: userExist._id }, process.env.SECRET_KEY, {
            expiresIn: process.env.JWT_EXPIRE,
        });
        return res.cookie({"token":token}).json({success:true,message:'LoggedIn Successfully'})
    } catch (error) {
        return res.json({ error: error });
    }

})

//Creating user routes to fetch users data
route.get('/user', async (req, res) => {
    try {
        const user  = await userModel.find();
        if(!user){
            return res.json({message:'No user found'})
        }
        return res.json({user:user})
    } catch (error) {
        return res.json({ error: error });  
    }
})

module.exports = route;

If you are using Async function, use try-catch block, otherwise it will throw an unhandled promise rejection error.

config.env

URI = 'mongodb+srv://ghulamrabbani883:[email protected]/?retryWrites=true&w=majority'
PORT = 5000
SECRET_KEY = KGGK>HKHVHJVKBKJKJBKBKHKBMKHB
JWT_EXPIRE = 2d

index.js

const express = require('express');
const dotenv = require('dotenv');

//Configure dotenv files above using any other library and files
dotenv.config({path:'./config/config.env'}); 
require('./config/conn');
//Creating an app from express
const app = express();
const route = require('./routes/userRoute');

//Using express.json to get request of json data
app.use(express.json());
//Using routes

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

//listening to the server
app.listen(process.env.PORT,()=>{
    console.log(`Server is listening at ${process.env.PORT}`);
})

Creating middleware to Authenticate user

What is middleware?

Middleware is a function that has access to the request, response object, and next function in the request-response cycle. The next function is invoked when the function execution is completed. As I mentioned above, use next() when you have to execute another callback function or middleware function.

Now create a folder named middleware, and inside it, create file name as auth.js and write the following code.

auth.js

const userModel = require('../models/userModel');
const jwt = require('jsonwebtoken');
const isAuthenticated = async (req,res,next)=>{
    try {
        const {token} = req.cookies;
        if(!token){
            return next('Please login to access the data');
        }
        const verify = await jwt.verify(token,process.env.SECRET_KEY);
        req.user = await userModel.findById(verify.id);
        next();
    } catch (error) {
       return next(error); 
    }
}

module.exports = isAuthenticated;

Now install the cookie-parser library to configure the cookieParser in your app. cookieParser helps you to access the token stored in the cookie. If you don’t have cookieParser configured in your nodejs app, you won’t be able to access the cookies from the headers of the request object. Now, write in the terminal to download cookie-parser.

npm i cookie-parser

Now, you have a cookieParser installed. Configure your app by modifying the index.js file and add middleware to the “/user/” route.

index.js file

const cookieParser = require('cookie-parser');
const express = require('express');
const dotenv = require('dotenv');

//Configure dotenv files above using any other library and files
dotenv.config({path:'./config/config.env'}); 
require('./config/conn');
//Creating an app from express
const app = express();
const route = require('./routes/userRoute');

//Using express.json to get request of json data
app.use(express.json());
//Configuring cookie-parser
app.use(cookieParser()); 

//Using routes
app.use('/api', route);

//listening to the server
app.listen(process.env.PORT,()=>{
    console.log(`Server is listening at ${process.env.PORT}`);
})

userRoute.js

//Requiring all the necessary files and libraries
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const isAuthenticated = require('../middleware/auth');

//Creating express router
const route = express.Router();
//Importing userModel
const userModel = require('../models/userModel');

//Creating user routes to fetch users data
route.get('/user', isAuthenticated, async (req, res) => {
    try {
        const user = await userModel.find();
        if (!user) {
            return res.json({ message: 'No user found' })
        }
        return res.json({ user: user })
    } catch (error) {
        return res.json({ error: error });
    }
})

module.exports = route;

The “/user” route only be accessible when the user is logged in.

Checking the APIs on POSTMAN

Before you check APIs, you need to modify the package.json file. Add the following lines of code.

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js",
    "dev": "nodemon index.js"
  },

You can start the server by typing npm start, but it will only run once. To keep your server running while changing files, you will need nodemon. Download it by typing in the terminal

npm install -g nodemon

-g flag will download the nodemon globally on your local system. You don’t have to download it again and again for every new projects.

To run the server, type npm run dev in the terminal. You will get the following result.

Finally, your code is completed, and the server is running correctly, go to postman and check if it is working.

What is POSTMAN?

POSTMAN is a software tool to design, build, develop and test API.

If you have not downloaded the postman on your computer, download it from the postman website.

Now open the postman and create a collection name nodeAPItest, and inside it, create three requests: register, login, and user. You should have the following files.

When you send JSON data to the “localhost:5000/api/register” you will get the following result.

As we are creating and saving tokens into cookies during register as well, you can have the user detail when you request the “localhost:5000/api/user” route. You can check the rest of the requests on POSTMAN.

If you want the the complete code you can get it from my github account.

Conclusion

In this tutorial, we have learned how to apply authentication to the NodeJS API using JWT tokens. We also authorized users to access the user data.

HAPPY CODING!