Authentication and Authorization with JWTs in Node && Express.js
Tags: javascript, node, express, typescript
Published: 2021-10-24 16:32:05

In this tutorial, we'll learn how to build an authentication system for a Nodejs & Express application using JWT. 

We'll be working on the project of this tutorial  [Build an API using Node, Express, MongoDB, and Docker ](https://dev.to/koladev/build-an-api-using-node-express-mongodb-and-docker-128p). You can find the code source for this tutorial  [here](https://github.com/koladev32/node-docker-tutorial).   

## What is Authentication and Authorization? 

Simply, authentication is the process of verifying the identity of someone. 

Authorization is the process of verifying what data the user can have access to.

And authorization only occurs when you've been authenticated. Then, the system will grant you access to the files you need.

## Setup the project

First of all, clone the project.

```
git clone https://github.com/koladev32/node-docker-tutorial.git
```

Once it's done, go inside the project and run. 

```
yarn install
```

Start the project using : 

```
yarn start
```

Inside the root of the project, create a `.env` file.

```
// .env
JWT_SECRET_KEY=)a(s3eihu+iir-_3@##ha$r$d4p5%!%e1==#b5jwif)z&kmm@7
```
You can easily generate a new value for this secret key online [here](https://djecrety.ir/).

## Creating the User model

Let's create the User model. But first, we need to define a type for this model. 

```ts
// src/types/user.ts
import { Document } from "mongoose";

export interface IUser extends Document {
  username: string;
  password: string;
  isAdmin: boolean;
}
```

Great, then we can write the User model.

```ts
// src/models/user.ts

import { IUser } from "../types/user";
import { model, Schema } from "mongoose";

const userSchema: Schema = new Schema(
  {
    username: {
      type: String,
      required: true,
      unique: true,
    },
    password: {
      type: String,
      required: true,
    },
    isAdmin: {
      type: Boolean,
      required: false,
      default: false,
    },
  },
  { timestamps: true }
);

export default model<IUser>("user", userSchema);
```

The User model is created. We can go and start writing the Login and Register controllers. 

### Registration 

Go to the `controllers` directory and create a new directory `users` which will contain a new `index.ts` file. 

Let write the `registerUser` controller.

```ts
// src/controllers/users/index.ts

import { Response, Request } from "express";
import { IUser } from "../../types/user";
import User from "../../models/user"
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
let refreshTokens: string[] = [];

const registerUser = async (
  req: Request,
  res: Response
): Promise<e.Response<any, Record<string, any>>> => {
  try {
    const { username, password } = req.body;
    if (!(username && password)) {
      return res.status(400).send("All inputs are required");
    }

    // Checking if the user already exists

    const oldUser = await User.findOne({ username });

    if (oldUser) {
      return res.status(400).send("User Already Exist. Please Login");
    }

    const user: IUser = new User({
      username: username,
    });

    const salt = await bcrypt.genSalt(10);
    // now we set user password to hashed password
    user.password = await bcrypt.hash(password, salt);

    user.save().then((doc) => {
      // Generating Access and refresh token
      const token = jwt.sign(
        { user_id: doc._id, username: username },
        process.env.JWT_SECRET_KEY,
        {
          expiresIn: "5min",
        }
      );

      const refreshToken = jwt.sign(
        { user_id: doc._id, username: username },
        process.env.JWT_SECRET_KEY
      );

      refreshTokens.push(refreshToken);

      return res.status(201).json({
        user: doc,
        token: token,
        refresh: refreshToken,
      });
    });

    return res.status(400).send("Unable to create user");
  } catch (error) {
    throw error;
  }
};

export {registerUser};
```
What are we doing here? 

- Check that the required fields have been provided
- Check that there is no existing user with the same username
- Creating the user and encrypting the password
- Generating refresh and access tokens
- Send responses

But why a refresh and an access token? 

When the token expires, the intuitive way to claim a new access token will be to log in again. But this is not effective at all for the experience of possible users. 
Then instead of login in again, the client can claim a new access token by making a request with the refresh token obtained at login or registration. 
We'll write the routes for this later.

Now, let's add this controller to the routes and register the new routes in our application.

```ts

// src/routes/index.ts

import { Router } from "express";
import {
  getMenus,
  addMenu,
  updateMenu,
  deleteMenu,
  retrieveMenu,
} from "../controllers/menus";
import {
  registerUser
} from "../controllers/users";

const menuRoutes: Router = Router();

const userRoutes: Router = Router();

// Menu Routes

menuRoutes.get("/menu", getMenus);
menuRoutes.post("/menu", addMenu);
menuRoutes.put("/menu/:id", updateMenu);
menuRoutes.delete("/menu/:id", deleteMenu);
menuRoutes.get("/menu/:id", retrieveMenu);

// User Routes

userRoutes.post("/user/register", registerUser);

export { menuRoutes, userRoutes };
```

And inside the `app.ts` file, let's use the new route.

```ts
// src/app.ts

import { menuRoutes, userRoutes } from "./routes";
...
app.use(cors());
app.use(express.json());

app.use(userRoutes);
...
```

The endpoint is available at localhost:4000/user/register.

![Register Insomnia](https://cdn.hashnode.com/res/hashnode/image/upload/v1635090486129/uKNeyge7s.png)

### Login 

Inside the `index.ts` file of users controllers, let's write the login function. 

```ts
// src/controllers/users/index.ts

const loginUser = async (
  req: Request,
  res: Response
): Promise<e.Response<any, Record<string, any>>> => {
  try {
    const { username, password } = req.body;
    if (!(username && password)) {
      return res.status(400).send("All inputs are required");
    }

    // Checking if the user exists

    const user: IUser | null = await User.findOne({ username });

    if (user && (await bcrypt.compare(password, user.password))) {
      // Create token
      const token = jwt.sign(
        { user_id: user._id, username: username },
        process.env.JWT_SECRET_KEY,
        {
          expiresIn: "5min",
        }
      );

      const refreshToken = jwt.sign(
        { user_id: user._id, username: username },
        process.env.JWT_SECRET_KEY
      );

      refreshTokens.push(refreshToken);

      // user
      return res.status(200).json({
        user: user,
        token: token,
        refresh: refreshToken,
      });
    }

    return res.status(400).send("Invalid Credentials");
  } catch (error) {
    throw error;
  }
};

export { registerUser, loginUser };
```

So what are we doing here? 

- Check that the required fields have been provided
- Check that the user exists
- Compare the password and create new tokens if everything is right
- Then send responses

If these validations are not done, we send error messages as well. 

Add it to the routes and log in using https://localhost:4500/user/login. 

```ts
// src/routes/index.ts

...
userRoutes.post("/user/login", loginUser);
...
```

## Protecting the Menu resources 

Ah great. The Login endpoint is done, the registering endpoint also is done. But the resources are not protected. 
You can still access them and because we need to write a middleware. 

A middleware is a function that is used to that acts as a bridge between a request and a function to execute the requests. 

Create a new directory named `middleware` inside `src` and create a file `index.ts`. 

Great, let's write our middleware. 

```ts
// src/middleware/index.ts

import e, { Response, Request, NextFunction } from "express";
import { IUser } from "../types/user";

const jwt = require("jsonwebtoken");

const authenticateJWT = async (
  req: Request,
  res: Response,
  next: NextFunction
): Promise<e.Response<any, Record<string, any>>> => {
  const authHeader = req.headers.authorization;

  if (authHeader) {
    const [header, token] = authHeader.split(" ");

    if (!(header && token)) {
      return res.status(401).send("Authentication credentials are required.");
    }

    jwt.verify(token, process.env.JWT_SECRET_KEY, (err: Error, user: IUser) => {
      if (err) {
        return res.sendStatus(403);
      }

      req.user = user;
      next();
    });
  }
  return res.sendStatus(401);
};

export default authenticateJWT;
```

What are we doing here? 

- Making sure there are authorization headers. We actually want the values of this header to this format: 'Bearer Token'. 
- Verifying the token and then creating a new key with `user` as value. `req.user = user`
- And finally using `next()` to execute the next function. 

Now, let's use the middleware in our application. 

```ts
// src/app.ts

import authenticateJWT from "./middleware";
...

app.use(userRoutes);

app.use(authenticateJWT);

app.use(menuRoutes);
...
```
Did you notice something? The middleware is placed after the `userRoutes` and before `menuRoutes`. 
Well, going like this, node & express will understand that the `userRoutes` are not protected and also that all the routes after the `authenticateJWT` will require an access token.

To test this, make a `GET` request to http://localhost:4000/menus without authorization header. You'll receive a `401` error.
Then use the access token from your previous login and add it to the authorization header. 
You should retrieve the menus.

### Refresh token 

It's time now to write the refresh token controller. 

```ts
// src/controllers/users/index.ts

const retrieveToken = async (
  req: Request,
  res: Response
): Promise<e.Response<any, Record<string, any>>> => {
  try {
    const { refresh } = req.body;
    if (!refresh) {
      return res.status(400).send("A refresh token is required");
    }

    if (!refreshTokens.includes(refresh)) {
      return res.status(403).send("Refresh Invalid. Please login.");
    }

    jwt.verify(
      refresh,
      process.env.JWT_SECRET_KEY,
      (err: Error, user: IUser) => {
        if (err) {
          return res.sendStatus(403);
        }

        const token = jwt.sign(
          { user_id: user._id, username: user.username },
          ")a(s3eihu+iir-_3@##ha$r$d4p5%!%e1==#b5jwif)z&kmm@7",
          {
            expiresIn: "5min",
          }
        );

        return res.status(201).send({
          token: token,
        });
      }
    );

    return res.status(400).send("Invalid Credentials");
  } catch (error) {
    throw error;
  }
};
```

So what are we doing here? 

- Making sure that the refresh token exists in the body
- Making sure that the refresh token exists in the memory of the server
- And finally verifying the refresh token then sending a new access token.

Add this new controller to the `userRoutes`. 

```ts
// src/routes/index.ts
...
userRoutes.post("/user/refresh", retrieveToken);
...
```

You can hit http://localhost:4000/user/refresh to retrieve a new access token.


![Insomnia refreshing token](https://cdn.hashnode.com/res/hashnode/image/upload/v1635091980048/nTzefx8AZ.png)

### Logout

But there is a problem. If the refresh token is stolen from the user, someone can use it to generate as many new tokens as they'd like. Let's invalidate this. 

```ts
// src/controllers/users/index.ts
...
const logoutUser = async (
  req: Request,
  res: Response
): Promise<e.Response<any, Record<string, any>>> => {
  try {
    const { refresh } = req.body;
    refreshTokens = refreshTokens.filter((token) => refresh !== token);

    return res.status(200).send("Logout successful");
  } catch (error) {
    throw error;
  }
};

export { registerUser, loginUser, retrieveToken, logoutUser };
```

And a new route to log out. 

```ts
// src/routes/index.ts

import {
  loginUser,
  logoutUser,
  registerUser,
  retrieveToken,
} from "../controllers/users";
...
userRoutes.post("user/logout", logoutUser);
...
```

You can hit http://localhost:4000/user/logout to invalidate the token.

And voilà, we're done. 🥳

## Conclusion 

In this article, we've learned how to build an authentication system for our Node & Express application using JWT.

And as every article can be made better so your suggestion or questions are welcome in the comment section. 😉

Check the code of this tutorial  [here](https://github.com/koladev32/node-express-jwt-tutorial).