Node.js REST API Security using JWT

By

Node.js RESTful APIs Security using JWT (JSON Web Tokens). Have you ever wondered and sat down to think of how authorization and authentication works?

Thinking about the complexity and all the implementation behind it? It’s not such scary, it’s a way of encrypting data and creating a unique token or an identifier that is sent to the client. Once the client makes a request, the unique token is sent along with the request and once it is valid, permission is granted.

Cool, it’s not that complex. We will learn a step-by-step implementation of the whole process. Let’s get started Ninjas!

What is Authentication and Authorization

As we know, security is vital in any kind of systems. With the increasing number of Cyber-attacks, we ought to be very careful with our systems. A lot of systems hold user’s data which it’s not ideal to get them exposed to hackers and other parties.

Authentication is simply granting access to a user by using either email, password or any other defined method. If the user enters email or password and seems to match with the one in record, he or she is granted permission.

On the other hand, authorization is access level of resources the authenticated users have. Example a normal user and an Admin has different access levels.

What’s is JWT?

It’s simply a stateless solution for authentication such that you don’t have to store state on the server. It is an encoded string that can be shared between the server and client. The string holds data which is referred to as the payload which allows the claim to be digitally signed. If you don’t get the concept, don’t worry. Once we start to implement, it will become easier for you.

How authentication works with JWT

Assuming that we already have a registered user in our database. The user makes a post request with email and password. The application checks whether such credentials exist and if so, a unique JWT for that user only is created using a secret key stored on the server.

The sever then sends back the JWT to the client which either stores it in cookie or local storage. The user is logged in without leaving any state on the server. The server has no information which user is logged in or not but the user knows he is logged in because he has a valid JWT which is like a passport to access part of the application.

Once the user needs to access protected parts, he sends a request with his JWT. It’s like showing a passport or pass-card in a party in order to join. Cool! Once the request hits the server, it checks whether the JWT is valid to identify whether the user is one who he says he is. All this connection must happen in a secured protocol.

How JWT looks like

how JWT looks like nodejs

It contains a header, payload and signature.

A signature is created by combination of the header, payload and the secret key.

Payload is any data that we can encode in a token. It could be user id or any other data.

Signature together with the header and payload forms the JWT.

The server verifies the token to ensure that the payload and the header were not changed. This will assure that the user is the one he or she claims to be.

In the verification process, the server takes header and payload from the token and combine them with the secret key on the server to create a test signature.

A test signature created is compared with the original signature. If it’s same, the data has not been modified and access can be granted.

Let’s get our hands dirty and write some code.

First, we need to create a project structure before writing some code. In this topic I will assume you know how to create directories and files. I will be using Unix based os and Vs code editor. The concept is similar in windows. Our main focus is to understand how JWT works and its implementation.

Create a NodeJS Project

Create a root directory and name it JWT or any name you wish. We have to install a couple of packages. Open your terminal in the root directory and run the following commands.

1. npm init- this initializes a json package on which you can save your packages.

2. npm install express -express will handle our routing.

3. npm install jsonwebtoken – this the package as the name describes.

4. npm install nodemon – this helps to restart the server for any change.

5.npm install mongoose -this will help us to communicate with the mongo db.

6. npm install dotenv -allows us to communicate with our environment

Have a folder structure similar to these.


First, we have to make a connection with the mongo db. Run your database and connect with compass or any GUI app of your choice. I shall be using mongo db compass locally. I assume at this point you have mongo db. installed and know how to run it.

Now it’s time to create our database and connect it. In your config.env file, paste the following code. It contains the database connection string and the secret key. It’s advisable to store such configuration information in a different environment where client has no access .

DATABASE_LOCAL=mongodb://localhost:27017/test
SECRET_KEY=this-is-my-super-secret-json-key-please-use-yours
EXPIRESIN=100d

Now in our app.js file, we are going to establish connection using mongoose. Our database connection string is from config.env file (DATABASE_LOCAL). Note that we have named our database ‘People’. Good! In app.js, write this code.

  
const express = require("express");
const app = express();
const mongoose = require("mongoose");
const dotenv = require("dotenv");
dotenv.config({ path: "./config.env" });

const userRoute = require("./userRoutes/userRoute");

app.use(express.json());
//Connecting with the database
mongoose
  .connect(process.env.DATABASE_LOCAL, {
    useNewUrlParser: true,
    useCreateIndex: true,
    useFindAndModify: false,
  })
  .then(() => console.log("connection established"));

//START OUR SERVER

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`listening at port:${port}`);
});

Now we have connection with the database. Next, we are going to create a user model in which we can use to register users. First, we have to define a schema then use the schema to create a record. In our userModels.js file. Write the following code. It creates a schema for us.

const mongoose = require("mongoose");
const validator = require("validator");

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    trim: true,
    required: [true, "You must include a Name"],
  },
  email: {
    type: String,
    required: [true, "You must include an Email"],
    unique: true,
    lowercase: true,
    validate: [validator.isEmail, "Must be be an email"],
  },
  photo: String,
  password: {
    type: String,
    required: [true, "You must include a password"],
    minlength: [4, "A password must atleast have 4 characters"],
    select: false,
  },
  passwordConfirm: {
    type: String,
    required: [true, "Must include password Confirmation"],
  },
});

const User = mongoose.model("Users", userSchema);

module.exports = User;

You can ignore validate method on line 14. To use it, make sure you install validator package using npm. Now we have User in which we can insert documents. In our auth.js,we will create a function that registers users and stores them in our database in user’s collection.

Paste the following code in auth.js

node secure using JWT

We shall be able to register users and store their details in database. Let’s define routes to our signup function. In userRoute.js paste the following code.

Okay. Now we are ready to use our route and create a new record to the database. Head on to your postman and under the body, select raw then JSON and also POST method. Here you will input your user schema details that is name, email, password and confirm password. You should notice that a record is created on your Users collection once you open mongo db compass.

Using JWT to login

In this part we are going to implement on how we said the JWT works. This is our main focus to understand how it really works. In our auth.js, we require jsonwebtoken package. First of all, in our signup function, we didn’t create a token. We are going to create a token once we register a user. A JWT receives arguments(payload). In our case we shall pass the id and secret key as our payload. Using the secret key from our config.env file and user id, then a token will be generated. The token will be associated with a specific user.

Add the following code in auth.js

const Jtoken = (id) => jwt.sign({ id }, process.env.SECRET_KEY);

I t is a method for creating a token. We required jsonwebtoken as ‘jwt’.The sign () method creates a unique token for that payload. In our case we are using the user id and the secret key. We also have a third argument expires In which we won’t cover in this post. It simply gives the time validity of the token.

Once we have our method in place, its time to use the method in signup and associate the unique token to the user.

In our signup function, modify it to include the token assignment method.

exports.signup = async (req, res) => {
  try {
    const newUser = await User.create({
      name: req.body.name,
      email: req.body.email,
      password: req.body.password,
      passwordConfirm: req.body.passwordConfirm,
    });

    const token = Jtoken(newUser._id);

    res.status(200).json({
      status: "success",
      token,
      data: {
        user: newUser,
      },
    });
  } catch (error) {
    return res.status(400).json({
      status: "fail",
      message: "An error occured.Make sure your email is unique",
    });
  }
};

With the Jtoken method used to generate JWT included, now we create a unique token once we register a user. The token is issued and used later to gain access if verification passes. Try again in postman to register a user and you should get a token as a response since we have included it in our response object.

We have successfully created a user and associated him with a token. Next thing is to try and login the user with the token to access protected route. In this case, we will create a middleware to protect our route. We will add a GET method to get all users from the database. Only the authenticated users will be able to read the database. We will use jwt to authenticate those users and enable them to get all the users from the database.

Here is a method that gets all the users from the database. Its route is /api/users/getall.

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

exports.getAllUsers = async (req, res) => {
  try {
    const users = await User.find();
    res.status(200).json({
      status: "success",

      data: {
        users,
      },
    });
  } catch (error) {
    console.log("Couldnt fetch users");
  }
};

This method should be a protected route and only the authenticated members should be able to view all the users. In order to protect the route, we create a protect middleware that will decide on whether the test fails or pass.

The middleware will check whether the request has the necessary headers. A JWT is stored with a name starting with Bearer or you can define yours too. You can check more on how to accomplish that. Once the request hits the middleware, it checks whether the request header contains the token. If there is no token, then it will raise an error and exit hence not moving to the next protected route.

If there is a token, it moves to the next process of checking whether the token is valid. This is the verify process. In the verification steps, it uses the jwt and the secret key to create the signature. If the test signature and original signature matches, then the test passes and the token is valid. It then moves to the next middleware and access the protected route.

To make our app more secure. We can add another advanced step. Once you get the results from verification, you can take the signature and check whether the id in the signature actually exists in the database. This is very important to avoid people who registered and are not in the database currently to access the route. If the id in the signature does not exist in database, then an error is raised and cannot access the route.
This is how we add a middleware to protect the route. It’s in auth.js

exports.protect = async (req, res, next) => {
  //check the token in headers by first checking whether it exists
  let token;
  if (
    req.headers.authorization &&
    req.headers.authorization.startsWith("Bearer")
  ) {
    token = req.headers.authorization.split(" ")[1];
  }
  if (!token) {
    return res.status(401).json({
      status: "fail",
      message: "login please to gain access",
    });
  }
  //Verifying the token to check its integrity
  const decoded = await promisify(jwt.verify)(token, process.env.SECRET_KEY);
  const currentUser = await User.findById(decoded.id);
  if (!currentUser) {
    return res.status(401).json({
      status: "fail",
      message: "User doesnt exist.Might be deleted lately!",
    });
  }
  next();
};

in our route, we place the middleware before.

In conclusion is very powerful in ensuring your application security. There are other options used like sessions but JWT is a better solution for many. I hope this post taught you something. You can now write your own applications and use JWT to secure them.