> For the complete documentation index, see [llms.txt](https://maissen.gitbook.io/maissen-grimoire/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://maissen.gitbook.io/maissen-grimoire/node-grimoire/node.js-with-nosql-databases/node.js-+-mongodb-user-authentication-and-authorization-with-jwt.md).

# Node.js + MongoDB: User Authentication & Authorization with JWT

### Node.js & MongoDB User Authentication example

We will build a Node.js Express application in that:

* User can signup new account, or login with username & password.
* By role (admin, moderator, user), the User has access to protected resources or not

These are APIs that we need to provide:

| Methods | Urls             | Actions                    |
| ------- | ---------------- | -------------------------- |
| POST    | /api/auth/signup | signup new account         |
| POST    | /api/auth/signin | login an account           |
| GET     | /api/test/all    | retrieve public content    |
| GET     | /api/test/user   | access User’s content      |
| GET     | /api/test/mod    | access Moderator’s content |
| GET     | /api/test/admin  | access Admin’s content     |

### Flow for Signup & Login with JWT Authentication

Following diagram shows you the flow that we’re gonna implement for User Registration, User Login and Authorization process.

![node-js-mongodb-jwt-authentication-flow](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-flow.png)

A legal JWT must be added to HTTP **x-access-token** Header if Client accesses protected resources.

### Node.js Express Architecture with Authentication & Authorization

Here is an overview of our Node.js Express App:

![node-js-mongodb-jwt-authentication-architecture](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-architecture.png)

Via *Express* routes, **HTTP request** that matches a route will be checked by **CORS Middleware** before coming to **Security** layer.

**Security** layer includes:

* JWT Authentication Middleware: verify SignUp, verify token
* Authorization Middleware: check User’s roles with record in database

An error message will be sent as HTTP response to Client when the middlewares throw any error, .

**Controllers** interact with MongoDB Database via *Mongoose* library and send **HTTP response** (token, user information, data based on roles…) to Client.

### Technology

* Express 4.17.1
* bcryptjs 2.4.3
* jsonwebtoken 8.5.1
* mongoose 5.9.1
* MongoDB

### Project Structure

This is directory structure for our Node.js Express & MongoDB application:

![node-js-mongodb-jwt-authentication-project-structure](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-project-structure.png)

### Create Node.js App

Create a folder for our project with command:

```
$ mkdir node-js-jwt-auth-mongodb
$ cd node-js-jwt-auth-mongodb
```

Then we initialize the Node.js App with a *package.json* file:

```
npm init

name: (node-js-jwt-auth-mongodb) 
version: (1.0.0) 
description: Node.js + MongoDB: JWT Authentication & Authorization
entry point: (index.js) server.js
test command: 
git repository: 
keywords: node.js, express, jwt, authentication, mongodb
author: bezkoder
license: (ISC)

Is this ok? (yes) yes
```

Let’s install necessary modules such as: `express`, `cors`, `body-parser`, `mongoose`, `jsonwebtoken` and `bcryptjs`.\
Run the command:

```
npm install express mongoose body-parser cors jsonwebtoken bcryptjs --save
```

Check *package.json* file, you can see it looks like this:

```
{
  "name": "node-js-jwt-auth-mongodb",
  "version": "1.0.0",
  "description": "Node.js + MongoDB: JWT Authentication & Authorization",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "node.js",
    "express",
    "jwt",
    "authentication",
    "mongodb"
  ],
  "author": "bezkoder",
  "license": "ISC",
  "dependencies": {
    "bcryptjs": "^2.4.3",
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "jsonwebtoken": "^8.5.1",
    "mongoose": "^5.9.1"
  }
}
```

### Setup Express web server

In the root folder, let’s create a new *server.js* file:

```
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");

const app = express();

var corsOptions = {
  origin: "http://localhost:8081"
};

app.use(cors(corsOptions));

// parse requests of content-type - application/json
app.use(bodyParser.json());

// parse requests of content-type - application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }));

// simple route
app.get("/", (req, res) => {
  res.json({ message: "Welcome to bezkoder application." });
});

// set port, listen for requests
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}.`);
});
```

What we’be just done in the code above:\
– import `express`, `body-parser` and `cors` modules:

* Express is for building the Rest apis
* [body-parser](https://www.npmjs.com/package/body-parser) helps to parse the request and create the `req.body` object
* [cors](https://www.npmjs.com/package/cors) provides Express middleware to enable CORS

– create an Express app, then add `body-parser` and `cors` middlewares using `app.use()` method. Notice that we set origin: `http://localhost:8081`.\
– define a GET route which is simple for test.\
– listen on port 8080 for incoming requests.

Now let’s run the app with command: `node server.js`.\
Open your browser with url <http://localhost:8080/>, you will see:

![node-js-mongodb-jwt-authentication-example-setup-server](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-example-setup-server.png)

### Configure MongoDB database

In the **app** folder, create **config** folder for configuration.

Then create a new *db.config.js* file that contains parameters for setting up MongoDB later:

```
module.exports = {
  HOST: "localhost",
  PORT: 27017,
  DB: "bezkoder_db"
};
```

### Define the Mongoose Model

In *models* folder, create `User` and `Role` data model as following code:

**models**/*role.model.js*

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

const Role = mongoose.model(
  "Role",
  new mongoose.Schema({
    name: String
  })
);

module.exports = Role;
```

**models**/*user.model.js*

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

const User = mongoose.model(
  "User",
  new mongoose.Schema({
    username: String,
    email: String,
    password: String,
    roles: [
      {
        type: mongoose.Schema.Types.ObjectId,
        ref: "Role"
      }
    ]
  })
);

module.exports = User;
```

These Mongoose Models represents **users** & **roles** collections in MongoDB database.\
`User` object will have a `roles` array that contains ids in **roles** collection as reference.

This kind is called *Reference Data Models* or *Normalization*. You can find more details at:\
[MongoDB One-to-Many Relationship tutorial with Mongoose examples](https://bezkoder.com/mongoose-one-to-many-relationship/)

After initializing Mongoose, we don’t need to write CRUD functions because Mongoose supports all of them:

* create a new User: object.[save()](https://mongoosejs.com/docs/api/model.html#model_Model-save)
* find a User by id: User.[findById(id)](https://mongoosejs.com/docs/api.html#model_Model.findById)
* find User by email: User.[findOne](https://mongoosejs.com/docs/api.html#model_Model.findOne)({ email: … })
* find User by username: User.findOne({ username: … })
* find all Roles which name in given `roles` array: Role.[find](https://mongoosejs.com/docs/api.html#model_Model.find)({ name: { $in: roles } })

These functions will be used in our Controllers and Middlewares.

### Initialize Mongoose

Now create **app**/**models**/*index.js* with content like this:

```
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;

const db = {};

db.mongoose = mongoose;

db.user = require("./user.model");
db.role = require("./role.model");

db.ROLES = ["user", "admin", "moderator"];

module.exports = db;
```

Open *server.js* and add following code to open Mongoose connection to MongoDB database:

```
...
const app = express();
app.use(...);

const db = require("./app/models");
const Role = db.role;

db.mongoose
  .connect(`mongodb://${dbConfig.HOST}:${dbConfig.PORT}/${dbConfig.DB}`, {
    useNewUrlParser: true,
    useUnifiedTopology: true
  })
  .then(() => {
    console.log("Successfully connect to MongoDB.");
    initial();
  })
  .catch(err => {
    console.error("Connection error", err);
    process.exit();
  });

...
function initial() {
  Role.estimatedDocumentCount((err, count) => {
    if (!err && count === 0) {
      new Role({
        name: "user"
      }).save(err => {
        if (err) {
          console.log("error", err);
        }

        console.log("added 'user' to roles collection");
      });

      new Role({
        name: "moderator"
      }).save(err => {
        if (err) {
          console.log("error", err);
        }

        console.log("added 'moderator' to roles collection");
      });

      new Role({
        name: "admin"
      }).save(err => {
        if (err) {
          console.log("error", err);
        }

        console.log("added 'admin' to roles collection");
      });
    }
  });
}
```

`initial()` function helps us to create 3 important rows in `roles` collection.

### Configure Auth Key

**jsonwebtoken** functions such as `verify()` or `sign()` use algorithm that needs a secret key (as String) to encode and decode token.

In the **app**/**config** folder, create *auth.config.js* file with following code:

```
module.exports = {
  secret: "bezkoder-secret-key"
};
```

You can create your own `secret` String.

### Create Middleware functions

To verify a Signup action, we need 2 functions:\
– check duplications for `username` and `email`\
– check if `roles` in the request is legal or not

**middlewares**/*verifySignUp.js*

```
const db = require("../models");
const ROLES = db.ROLES;
const User = db.user;

checkDuplicateUsernameOrEmail = (req, res, next) => {
  // Username
  User.findOne({
    username: req.body.username
  }).exec((err, user) => {
    if (err) {
      res.status(500).send({ message: err });
      return;
    }

    if (user) {
      res.status(400).send({ message: "Failed! Username is already in use!" });
      return;
    }

    // Email
    User.findOne({
      email: req.body.email
    }).exec((err, user) => {
      if (err) {
        res.status(500).send({ message: err });
        return;
      }

      if (user) {
        res.status(400).send({ message: "Failed! Email is already in use!" });
        return;
      }

      next();
    });
  });
};

checkRolesExisted = (req, res, next) => {
  if (req.body.roles) {
    for (let i = 0; i < req.body.roles.length; i++) {
      if (!ROLES.includes(req.body.roles[i])) {
        res.status(400).send({
          message: `Failed! Role ${req.body.roles[i]} does not exist!`
        });
        return;
      }
    }
  }

  next();
};

const verifySignUp = {
  checkDuplicateUsernameOrEmail,
  checkRolesExisted
};

module.exports = verifySignUp;
```

To process Authentication & Authorization, we create following functions:\
\- check if `token` is provided, legal or not. We get token from **x-access-token** of HTTP headers, then use **jsonwebtoken**'s `verify()` function\
\- check if `roles` of the user contains required role or not

**middlewares**/*authJwt.js*

```
const jwt = require("jsonwebtoken");
const config = require("../config/auth.config.js");
const db = require("../models");
const User = db.user;
const Role = db.role;

verifyToken = (req, res, next) => {
  let token = req.headers["x-access-token"];

  if (!token) {
    return res.status(403).send({ message: "No token provided!" });
  }

  jwt.verify(token, config.secret, (err, decoded) => {
    if (err) {
      return res.status(401).send({ message: "Unauthorized!" });
    }
    req.userId = decoded.id;
    next();
  });
};

isAdmin = (req, res, next) => {
  User.findById(req.userId).exec((err, user) => {
    if (err) {
      res.status(500).send({ message: err });
      return;
    }

    Role.find(
      {
        _id: { $in: user.roles }
      },
      (err, roles) => {
        if (err) {
          res.status(500).send({ message: err });
          return;
        }

        for (let i = 0; i < roles.length; i++) {
          if (roles[i].name === "admin") {
            next();
            return;
          }
        }

        res.status(403).send({ message: "Require Admin Role!" });
        return;
      }
    );
  });
};

isModerator = (req, res, next) => {
  User.findById(req.userId).exec((err, user) => {
    if (err) {
      res.status(500).send({ message: err });
      return;
    }

    Role.find(
      {
        _id: { $in: user.roles }
      },
      (err, roles) => {
        if (err) {
          res.status(500).send({ message: err });
          return;
        }

        for (let i = 0; i < roles.length; i++) {
          if (roles[i].name === "moderator") {
            next();
            return;
          }
        }

        res.status(403).send({ message: "Require Moderator Role!" });
        return;
      }
    );
  });
};

const authJwt = {
  verifyToken,
  isAdmin,
  isModerator
};
module.exports = authJwt;
```

**middlewares**/*index.js*

```
const authJwt = require("./authJwt");
const verifySignUp = require("./verifySignUp");

module.exports = {
  authJwt,
  verifySignUp
};
```

### Create Controllers

#### Controller for Authentication

There are 2 main functions for Authentication:\
\- `signup`: create new User in database (role is **user** if not specifying role)\
\- `signin`:

* find `username` of the request in database, if it exists
* compare `password` with `password` in database using **bcrypt**, if it is correct
* generate a token using **jsonwebtoken**
* return user information & access Token

**controllers**/*auth.controller.js*

```
const config = require("../config/auth.config");
const db = require("../models");
const User = db.user;
const Role = db.role;

var jwt = require("jsonwebtoken");
var bcrypt = require("bcryptjs");

exports.signup = (req, res) => {
  const user = new User({
    username: req.body.username,
    email: req.body.email,
    password: bcrypt.hashSync(req.body.password, 8)
  });

  user.save((err, user) => {
    if (err) {
      res.status(500).send({ message: err });
      return;
    }

    if (req.body.roles) {
      Role.find(
        {
          name: { $in: req.body.roles }
        },
        (err, roles) => {
          if (err) {
            res.status(500).send({ message: err });
            return;
          }

          user.roles = roles.map(role => role._id);
          user.save(err => {
            if (err) {
              res.status(500).send({ message: err });
              return;
            }

            res.send({ message: "User was registered successfully!" });
          });
        }
      );
    } else {
      Role.findOne({ name: "user" }, (err, role) => {
        if (err) {
          res.status(500).send({ message: err });
          return;
        }

        user.roles = [role._id];
        user.save(err => {
          if (err) {
            res.status(500).send({ message: err });
            return;
          }

          res.send({ message: "User was registered successfully!" });
        });
      });
    }
  });
};

exports.signin = (req, res) => {
  User.findOne({
    username: req.body.username
  })
    .populate("roles", "-__v")
    .exec((err, user) => {
      if (err) {
        res.status(500).send({ message: err });
        return;
      }

      if (!user) {
        return res.status(404).send({ message: "User Not found." });
      }

      var passwordIsValid = bcrypt.compareSync(
        req.body.password,
        user.password
      );

      if (!passwordIsValid) {
        return res.status(401).send({
          accessToken: null,
          message: "Invalid Password!"
        });
      }

      var token = jwt.sign({ id: user.id }, config.secret, {
        expiresIn: 86400 // 24 hours
      });

      var authorities = [];

      for (let i = 0; i < user.roles.length; i++) {
        authorities.push("ROLE_" + user.roles[i].name.toUpperCase());
      }
      res.status(200).send({
        id: user._id,
        username: user.username,
        email: user.email,
        roles: authorities,
        accessToken: token
      });
    });
};
```

#### Controller for testing Authorization

There are 4 functions:\
– `/api/test/all` for public access\
– `/api/test/user` for loggedin users (any role)\
– `/api/test/mod` for **moderator** users\
– `/api/test/admin` for **admin** users

**controllers**/*user.controller.js*

```
exports.allAccess = (req, res) => {
  res.status(200).send("Public Content.");
};

exports.userBoard = (req, res) => {
  res.status(200).send("User Content.");
};

exports.adminBoard = (req, res) => {
  res.status(200).send("Admin Content.");
};

exports.moderatorBoard = (req, res) => {
  res.status(200).send("Moderator Content.");
};
```

Let's combine middlewares with controller functions in the next section.

### Define Routes

When a client sends request for an endpoint using HTTP request (GET, POST, PUT, DELETE), we need to determine how the server will response by setting up the routes.

We can separate our routes into 2 part: for Authentication and for Authorization (accessing protected resources).

**Authentication:**

* POST `/api/auth/signup`
* POST `/api/auth/signin`

**routes**/*auth.routes.js*

```
const { verifySignUp } = require("../middlewares");
const controller = require("../controllers/auth.controller");

module.exports = function(app) {
  app.use(function(req, res, next) {
    res.header(
      "Access-Control-Allow-Headers",
      "x-access-token, Origin, Content-Type, Accept"
    );
    next();
  });

  app.post(
    "/api/auth/signup",
    [
      verifySignUp.checkDuplicateUsernameOrEmail,
      verifySignUp.checkRolesExisted
    ],
    controller.signup
  );

  app.post("/api/auth/signin", controller.signin);
};
```

**Authorization:**

* GET `/api/test/all`
* GET `/api/test/user` for loggedin users (user/moderator/admin)
* GET `/api/test/mod` for moderator
* GET `/api/test/admin` for admin

**routes**/*user.routes.js*

```
const { authJwt } = require("../middlewares");
const controller = require("../controllers/user.controller");

module.exports = function(app) {
  app.use(function(req, res, next) {
    res.header(
      "Access-Control-Allow-Headers",
      "x-access-token, Origin, Content-Type, Accept"
    );
    next();
  });

  app.get("/api/test/all", controller.allAccess);

  app.get("/api/test/user", [authJwt.verifyToken], controller.userBoard);

  app.get(
    "/api/test/mod",
    [authJwt.verifyToken, authJwt.isModerator],
    controller.moderatorBoard
  );

  app.get(
    "/api/test/admin",
    [authJwt.verifyToken, authJwt.isAdmin],
    controller.adminBoard
  );
};
```

Don't forget to add these routes in *server.js*:

```
...
// routes
require('./app/routes/auth.routes')(app);
require('./app/routes/user.routes')(app);

// set port, listen for requests
...
```

### Run & Test with Results

Run Node.js application with command: `node server.js`.

The console shows:

```
Server is running on port 8080.
Successfully connect to MongoDB.
added 'user' to roles collection
added 'admin' to roles collection    
added 'moderator' to roles collection
```

Let's check `roles` collection in MongoDB database:

![node-js-mongodb-jwt-authentication-roles-collection](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-roles-collection.png)

Register some users with `/signup` API:

* **admin** with `admin` role
* **modera** with `moderator` and `user` roles
* **bezkoder** with `user` role

![node-js-mongodb-jwt-authentication-example-users-registration](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-example-users-registration.png)

`users` collection after signup could look like this.

![node-js-mongodb-jwt-authentication-example-users-collection](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-example-users-collection.png)

**Access public resource:** GET `/api/test/all`

![node-js-mongodb-jwt-authentication-example-public-resourses](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-example-public-resourses.png)

**Access protected resource:** GET `/api/test/user`

![node-js-mongodb-jwt-authentication-example-protected-resourses](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-example-protected-resourses.png)

**Login an account (with wrong password):** POST `/api/auth/signin`

![node-js-mongodb-jwt-authentication-example-login-wrong-password](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-example-login-wrong-password.png)

**Login an legal account:** POST `/api/auth/signin`

![node-js-mongodb-jwt-authentication-example-login-successful](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-example-login-successful.png)

**Access protected resources:** GET `/api/test/user`

![node-js-mongodb-jwt-authentication-example-access-resourses](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-example-access-resourses.png)

GET `/api/test/admin`

![node-js-mongodb-jwt-authentication-example-access-admin](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-example-access-admin.png)

If we use a wrong access token:

![node-js-mongodb-jwt-authentication-example-wrong-token](https://bezkoder.com/wp-content/uploads/2020/02/node-js-mongodb-jwt-authentication-example-wrong-token.png)
