How to Set Up Authentication and Authorization in Express Using JWT

Did you notice that, once we logged in or authenticated on Facebook, from a browser, they no longer tend us to authenticate again until we logout? Here, a unique token is generated and saved in the local storage of our browser just after we register/log in. This token is used for authentication and authorization processes. In this article, we will discuss authentication, authorization, and implementing it in an Express app using JWT.

So we did discuss about a token. This token contains the information about our account and it is necessary to send this token with all the request that needs authorization.

Most part of this process is coded in the backend. We can learn about this in the upcoming steps.

What are authentication and authorization?

Authentication confirms that the person is the one who says they are. Authorization gives permission to users for accessing the contents.

Let’s explain these using a real-world example. Assume you are going to login to Facebook. It first shows you a sign-in page to enter your email and password to authenticate. After authentication, you can access your friend’s list, settings, profile, etc. But others can not access it. So this is the right you got after authentication which is called authorization.

Now, as a developer, we must understand what is happening on the programming side at the time of authentication and authorization.

When we create an account with Facebook, it generates a token containing our information from the backend. It will be a string shown below.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZjZkZGRhZmFmMjU3ZTJjZmM0ZmQzMDciLCJ1c2VyVHlwZSI6ImN1c3RvbWVyIiwiaWF0IjoxNjAxMDM1Njk2LCJleHAiOjE2MDE4OTk2OTZ9.THThP93-fpg6Es4OI--6YtnN-nJGUVFHKez-Ka-GP-M

This string will send to our browser and store in its local storage or cookies storage. So, the authentication part is complete.

Now, we try to access our settings page. But Facebook needs to authorize it’s the same person who signed up before to show you the page.

Here the browser sends the token with the request for settings page to be backend side.

From there, Facebook backend decrypts the token and finds the user details. If it’s the same person, it allows the request to process and sends the response. Otherwise, it shows error.

We are implementing authentication and authorization in an Express app using JWT in the same manner.

What is JWT?

So we have discussed what is authorization and authentication. But what is the role of JWT here?

JSON Web Tokens or JWT is a package used to create encrypted access tokens using user details and some secret keys. It can also decrypt the same token and sort out the user data.

How to set up authentication and authorization in Express.js using JWT?

Authentication and authorization processes are done from the backend side. Here in this article, we are using an Express backend to set up authentication and authorization using JWT

We will create a simple Express backend with a user schema, create a register, login, and profile route for users.

We will set up the code for registering new users, login, and route for updating the user profile with JWT authorization.

1. Setup MongoDB, Compass and create a database

In this project, we are using MongoDB to store data and so that it must install in our system.

To graphically manage the MongoDB databases, install MongoDB Compass also.

Then create a database and collection for our project.

The below steps will help us for doing these.

1.1 Install MongoDB

Refer MongoDB Installation guide for Windows 10 to install MongoDB on Windows 10. If you are using any other OS, the refer the official documentation Install MongoDB.

After running the MongoDB we will see a screen as below.

1.2 Install MongoDB Compass

MongoDB stores the data in the form of documents. So, to view those data, and edit, we can use a graphical user interface tool for MongoDB which is MongoDB Compass.

You can refer the official website to Install MongoDB Compass.

After the installation it will open the screen as below.

Just continue with the default option. It connects with the MongoDB running in our local system at port 27017.

Create a new database with name simplebackend and collection name users.

2. Create an Express.js Project

We can create an express.js app from scratch using npm easily. Follow the below steps.

2.1 Install Nodejs

Installing Nodejs on our system may differ with our Operating System.

  • macOS and Windows users, use the below link to download the Nodejs Installation file.
https://nodejs.org/en/download/
  • For Ubuntu users, Nodejs can install with the below commands.
curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt-get install -y nodejs
  • For other operating systems, the installation instructions are available with the link below.
https://nodejs.org/en/download/package-manager/

2.2 Install Nodemon globally

Nodemon is a tool that helps develop node-based applications by automatically restarting the application when a changes in the directory are detected.

npm i -g nodemon

2.3 Create a Project Directory

Create a project directory and navigate to it using our Command Prompt/Terminal.

mkdir express-backend
cd express-backend

2.4 Initialize a Node.js Project

It requires a package.json file inside our project directory to work with Node.js projects. So we need to initialize a Node.js project using the below command.

npm init

2.5 Install the necessary packages

Now inside our project, we need to install the necessary packages express, cors, body-parser, and morgan.

npm i express cors body-parser morgan

3. Configure the root file (index.js)

So we have created an express.js project. Now we can open it using a code editor. I am using the Visual Studio Code.

The above image shows the file structure of our express app.

Now start the project by creating an index.js file and import all the packages we have installed earlier. Also, define a port number to start the app.

const express = require("express");
const app = express();
const cors = require("cors");
const bodyParser = require("body-parser");
const logger = require("morgan");
const mongoose = require("mongoose");
const port = process.env.PORT || 3001;

app.use(logger("dev"));

We are using MongoDB as the database and so the DB details should add in the index.js.

let dbUrl = "mongodb://localhost:27017/simplebackend";
var options = {
  keepAlive: 1,
  connectTimeoutMS: 30000,
  useNewUrlParser: true,
  useUnifiedTopology: true,
};

mongoose.connect(dbUrl, options, (err) => {
if (err) console.log(err);
});

Use the packages that we imported earlier. Here I am not explaining the duty of these packages. If you really want to know it, then refer to the article How To Build A Simple REST API With Node/Express

app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use("/users", users);
app.listen(port, function () {
console.log("Runnning on " + port);
});

module.exports = app;

So that the complete index.js file looks as below.

4. Create a User schema

Now create a model directory and add a file User.js for User schema. In this file, we will also add the generatePasswordHash function and validatePassword function as part of authentication.

  • generatePasswordHash function creates a hashed password using the password we are adding while registering an account.
  • validatePassword function hashes the password and validates it with the one that stored in DB while logging in.

We also add three arrays fillable, returnable and publicReturnable.

  • The fillable array contains schema fields that can fill directly from request body parameters.
  • The returnable array contains schema fields that will include with a response.

So the complete User.js file will looks as below.

Inside the model directory, we also add an index.js file for easily importing the models to other files.

const User = require("./User");
module.exports = {
  User,
};

5. Create a config file

Now we want to create a config.js file in the root directory to store the secret keys and sensitive pieces of information.

We use it to store a secret key that is used for creating an encrypted token by JWT here. This secret value should not be lost. Because it is needed for decrypting these tokens.

6. Create routes

If an API is called from the frontend, it reaches the index.js file first and from there, it is directed to the routes directory.

6.1 index.js

At first, we are creating an index.js file inside routes/users directory for an easy workflow. All the API URLs for users path reaches here first. Then it is directed according to the URL path.

6.2 register.js

In this article, we are dealing with authentication and authorization. So, the registration step is really important.

When the registration happens, the request reaches the route register. From here, it creates a new user and sends the user document as the response. With the response, it adds a token generated by JWT.

The code below is doing this task. It takes the user id, a secret key, an expiry duration, and returns a hashed token. This token will include in the registration response.

Here I used 10d as expiry duration so that the token will be expired after 10 days. But no worries about it because it will be renewed automatically after expiration.

user.token = jwt.sign(
   {
      _id: user._id,
   },
      config.secret,
   {
      expiresIn: "10d",
   }
);

So that the complete register.js file looks as below.

Here, a hashed password is not needed with the response. We have already declared some user fields that can return inside the User model. So, the fields inside User.returnable will only return with the response.

6.3 login.js

The same process happens in the case of a login. It finds the user document with email in the body. If the password in the body is correct, it sends the details of the user as a response.

But the frontend wants a token too with the response for the authorization process. So here we create a token as we did in the registration step.

The complete login.js file looks the same as below.

So the authentication is done. Now we can enter to the authorization part.

6.4 profile.js

The profile route contains 3 API URLs that I explained below.

1. GET /public/:userId

This API takes userId as a req parameter and returns the user profile data to the public. We have already setup User.publicReturnable. These fields will return with the response.

Anyone can access this API. But we will not get some data like user’s email.

router.get("/public/:userId", async (req, res) => {
  try {
    let user = await models.User.findOne({
      _id: req.params.userId,
    });
    res.status(200).json({
      data: _.pick(user, models.User.publicReturnable),
    });
  } catch (err) {
    console.log(err);
    res.status(400).json({
      message: "Some error occured",
      err,
    });
  }
});

2. GET /

This API returns the user details with the fields inside User.returnable. A user can access his own data. The guests can not even get to the API.

We are adding a jwtauth middleware in this route. It is doing this restriction. It checks whether the request contains an x-access-token and it is valid or not. If it’s valid, finds the user associated with the token and add the data in req.user.

This process is called authorization.

So, from the API, we will find the user with _id, req.user._id and respond with this data.

router.get("/", [jwtauth], async (req, res) => {
  try {
    let user = await models.User.findOne({
      _id: req.user._id,
    });
    res.status(200).json({
      data: _.pick(user, models.User.returnable),
    });
  } catch (err) {
    console.log(err);
    res.status(400).json({
      message: "Some error occured",
      err,
    });
  }
});

3. PUT /

The above authorization processes are applicable to this API also. The user can only change his own data. and guests can not even access to it.

router.put("/", [jwtauth], async (req, res) => {
  try {
    let user = await models.User.findOne({
      _id: req.user._id,
    });
    user = _.merge(user, _.pick(req.body, models.User.fillable));
    user = await user.save();
    user = _.pick(user, models.User.returnable);
    res.status(200).json({
      data: _.pick(user, models.User.returnable),
    });
  } catch (err) {
    res.status(400).json({
      message: "Some error occured",
      err,
    });
  }
});

So that the complete profile.js looks as below.

7. Set up JWT token validation

This is one of the important steps in this article. Here we are validating the JWT token that is sending in the header named as x-access-token.

So create folder called lib and a file jwtlib.js. It will contain the code as below.

So at last our project file structure will looks the same as below.

8. Test the APIs

So our Express backend is ready with authentication and authorization. Now we can test these APIs.

We can use Postman tool for testing the APIs.

8.1 Register

We can make a POST request to http://localhost:3001/users/register to register a new user. Here I am using the below body parameters.

{
    "name": "Syamlal",
    "email": "[email protected]",
    "password": "password"
}

This will register a new user and returns a response from backend like below included with a unique token.

{
    "data": {
        "_id": "5f7c27291f11b52378a53630",
        "name": "Syamlal",
        "email": "[email protected]",
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZjdjMjcyOTFmMTFiNTIzNzhhNTM2MzAiLCJpYXQiOjE2MDE5NzIwMDksImV4cCI6MTYwMjgzNjAwOX0.0N7Y5iDMW_prDOq5Q-THJz5kpCKM4fgnT8DPmznrdt0"
    }
}

8.2 Login

We can make a POST request to http://localhost:3001/users/login for logging in to the account. It takes the below parameters.

{
    "email": "[email protected]",
    "password": "password"
}

The response will be as below.

{
    "status": 200,
    "data": {
        "_id": "5f7c26fc16401b3aa425ae6b",
        "name": "Syamlal",
        "email": "[email protected]",
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZjdjMjZmYzE2NDAxYjNhYTQyNWFlNmIiLCJpYXQiOjE2MDE5OTI3NzUsImV4cCI6MTYwMjg1Njc3NX0.HluWGj_q7Ypn6uWcGtS2RW-RU1PNFzF0A3ssX6r6xUo"
    }
}

8.3 Get profile data for the public

Profile data for the public can be get using the API URL http://localhost:3001/users/profile/public/5f7c26fc16401b3aa425ae6b, where 5f7c26fc16401b3aa425ae6b is the user id.

It does not need any token for this data.

8.4 Get profile data for the user

Here, the user needs almost all data including email. So before accessing this API, the user needs to authorize using a token. You can use any name for sending this token as the header, but the name must be the same in the jwtlib.js which is there in the 7th step. Here I am using the name x-access-token.

So here we will make a GET request to the API URL http://localhost:3001/users/profile/. But remember that this request requires an x-access-token in the header.

x-access-token is actually the JWT token that we are getting from the response of register and login.

8.5 Update user data

This API also requires a token for authorization. Because a user can update his own data only.

Here I’m updating the name from Syamlal to Robin.

Here I also included a header x-access-token to authorize the user.

GitHub

The complete project here we made is uploaded in GitHub for reference.

https://github.com/techomoro/AuthenticationAndAuthorizationExpressJWT

Have a nice code!

Summary

In this article, we learned the steps for implementing authentication and authorization in an Express app using JWT. We started with creating a simple Express project. Created routes for managing a user profile. We added a middleware called jwtauth to restrict access to some API URLs. Also, we have tested these APIs using the Postman tool.

Be the first to reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.