Authentication Role Permission API using Node Express MySQL

by Didin J. on Oct 03, 2021 Authentication Role Permission API using Node Express MySQL

The comprehensive step by step Node, Express.js, Passport.js, Sequelize.js, and MySQL/MariaDB/ClearDB API authentication with role-based permissions

In the previous tutorial of Node Express Sequelize, we are using simple authentication just using username/email and password that return JWT token. Now, there is a little wide functionality by adding roles and permissions for the user. So, the Rest API endpoint will accessible by authenticated and authorized users only.

Table of Contents:

  1. Create Express.js Project and Install Required Dependencies
  2. Add and Configure Sequelize.js and MySQL
  3. Create or Generate Sequelize Models and Migrations
  4. Add and Configure Passport.js
  5. Add Permissions Checker
  6. Create Routers for User, Role, Permissions, and Products CRUD
  7. Create Routers for Authentication
  8. Run and Test Authentication Role Permission API using Node Express MySQL

The flow of the Node Express REST API is very simple. Every REST API endpoint is restricted by Authentication and Authorization. Authentication finds the matching username and password from the User model and Authorization find the matching role that has permissions to the specific REST API endpoint. So, the relation of the User, Role, and Permission model will be: 

  1. User has one role
  2. Role has many permissions
  3. Each secure endpoint has a permissions

By that, the role will have a different list of permissions and the secure endpoint will have one or more permissions (for easy maintaining use one permission for each endpoint). For this tutorial, we will apply the role for the user while registering and apply permissions for each role when the application initializes. You can create your own user and role management CMS to make user, role, and permissions management easy.

We assume that you have installed MySQL/MariaDB/ClearDB server in your machine or can use your own remote Database server as well. Also, you have installed Node.js on your machine and can run the `node`, `npm`, or `yarn` command in your terminal or command line. Next, check their version by type these commands in your terminal or command line.

node -v
v14.16.0
npm -v
6.14.11
yarn -v
1.22.10

You can watch the video tutorial on our YouTube channel here.

That is the version of Node.js/NPM/Yarn that we are using right now. Let’s get started with the main step!


1. Create Express.js Project and Install Required Dependencies

Using Express.js with a lot of HTTP utility methods and middleware at your disposal, creating a robust API is quick and easy. Open your terminal or node command line the go to your projects folder. First, install express-generator using this command.

sudo npm install express-generator -g

Next, create an Express.js app using this command.

express node—auth-role-permissions view=ejs

This will create the Express.js project with the EJS view instead of the Jade view template because using the '--view=ejs' parameter. Next, go to the newly created project folder then install node modules.

cd node—auth-role-permissions

You should see the folder structure like this.

├── app.js
├── bin
│   └── www
├── node_modules
├── package-lock.json
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.jade
    ├── index.jade
    └── layout.jade

There's no view yet using the latest Express generator. We don't need it because we will create a RESTful API. Next, install the required dependencies for this Express.js application such as Brcypt.js, JSONWebToken, MySQL2, Passport.js, Passport-JWT, Sequelize, and optional Sequelize-Auto.

npm i bcryptjs jsonwebtoken mysql2 passport passport-jwt sequelize sequelize-auto http-errors


2. Add and Configure Sequelize.js and MySQL

We will use Sequelize CLI to generate models and migrations. For that, install the Sequelize CLI by type this command.

sudo npm install -g sequelize-cli

Sequelize and MySQL module or library already installed in the previous step. Now, we will configure Sequelize and database connection. First, create a new file at the root of the project folder that initializes the Sequelize configuration.

touch .sequelizerc

Open and edit that file then add these lines of codes.

const path = require('path');

module.exports = {
  "config": path.resolve('./config', 'config.json'),
  "models-path": path.resolve('./models'),
  "seeders-path": path.resolve('./seeders'),
  "migrations-path": path.resolve('./migrations')
};

That files will tell Sequelize initialization to generate config, models, seeders, and migrations files to specific directories.  Next, type this command to initialize the Sequelize.

sequelize init

That command will create `config/config.json`, `models/index.js`, `migrations`, and `seeders` directories and files. Next, open and edit `config/config.json` then make it like this.

{
  "development": {
    "username": "root",
    "password": "root",
    "database": "node_app",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": "root",
    "database": "node_app_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": "root",
    "database": "node_app_production",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}

Next, we need to create a MySQL Database for this application. If you are using MySQL/Maria
DB CLI type this command to enter MySQL console.

mysql -uroot -p

Create a new Database by this command.

CREATE DATABASE node_app;

Quit MySQL console then continue to the next step.


3. Create or Generate Sequelize Models and Migrations

We will generate all required models that represent the MySQL tables for this application.  For that, type this Sequelize command to generate User, Role, Permission, Product.

sequelize model:create --name User --attributes role_id:integer,email:string,password:string,fullname:string,phone:string

sequelize model:create --name Role --attributes role_name:string,role_description:string

sequelize model:create --name Permission --attributes perm_name:string,perm_description:string

sequelize model:create --name RolePermission --attributes role_id:integer,perm_id:integer

sequelize model:create --name Product --attributes prod_name:integer,prod_description:string,prod_image:string,prod_price:string

Those commands just generate the required fields for each model but not the relationship. To add the relationship for each model, we need to add them manually. Open and edit models/user.js then add these lines inside the class User associate method.

  class User extends Model {
    static associate(models) {
      User.hasOne(models.Role, {
        foreignKey: 'user_id',
        as: 'role',
      });
    }
  };

Also, we need to improve the init methods to add validation to all fields.

  User.init({
    role_id: DataTypes.INTEGER,
    email: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false
    },
    fullname: {
      type: DataTypes.STRING,
      allowNull: false
    },
    phone:  {
      type: DataTypes.STRING,
      allowNull: true
    }
  }, {
    sequelize,
    modelName: 'User',
  });

That improvement should apply to migration files too. Open and edit xxx-create-user.js from the migrations folder. Then replace the create table method with this.

    await queryInterface.createTable('Users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      role_id: {
        type: Sequelize.INTEGER
      },
      email: {
        type: Sequelize.STRING,
        allowNull: false,
        unique: true
      },
      password: {
        type: Sequelize.STRING,
        allowNull: false,
      },
      fullname: {
        type: Sequelize.STRING,
        allowNull: false,
      },
      phone: {
        type: Sequelize.STRING,
        allowNull: true,
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });

Open and edit models/role.js then add these lines inside the class Role associate method.

  class Role extends Model {
    static associate(models) {
      Role.belongsTo(models.User, {
        foreignKey: 'user_id',
        as: 'user'
      });
      Role.belongsToMany(models.Permission, {
        through: 'RolePermission',
        as: 'permissions',
        foreignKey: 'role_id'
      });
    }
  };

Also, replace these lines.

  Role.init({
    role_name: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true
    },
    role_description:  {
      type: DataTypes.STRING,
      allowNull: false
    }
  }, {
    sequelize,
    modelName: 'Role',
  });

And the create table method in the role migration file.

    await queryInterface.createTable('Roles', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      role_name: {
        type: Sequelize.STRING,
        allowNull: false,
        unique: true
      },
      role_description: {
        type: Sequelize.STRING,
        allowNull: true
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });

Open and edit models/permission.js then add these lines inside the class Permission associate method.

  class Permission extends Model {
    static associate(models) {
      Permission.belongsToMany(models.Role, {
        through: 'RolePermission',
        as: 'roles',
        foreignKey: 'perm_id'
      });
    }
  };

Also, refine all fields like this.

  Permission.init({
    perm_name: {
      type: DataTypes.STRING,
      unique: true,
      allowNull: false
    },
    perm_description:  {
      type: DataTypes.STRING,
      allowNull: false
    }
  }, {
    sequelize,
    modelName: 'Permission',
  });

And create the permissions migration file in create table methods.

    await queryInterface.createTable('Permissions', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      perm_name: {
        type: Sequelize.STRING,
        unique: true,
        allowNull: false
      },
      perm_description: {
        type: Sequelize.STRING,
        unique: false
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });

Finally, for other models and migrations, there's nothing to change, and they are all ready to generate the table to the PostgreSQL Database. Type this command to generate the table to the database using Sequelize.

sequelize db:migrate

So, the MySQL table structure will be like this.

Authentication Role Permission API using Node Express MySQL - DB structure


4. Add and Configure Passport.js

We have already installing Passport.js, JWT, and Bcrypt in the first step. Now, we will configure Passport.js to make authentication working. First, we need to define JWT Strategy that using by Passport.js and extract the JWT token as a user id that will use to get a User by ID. For that, add a file inside the config directory.

touch config/passport.js

Open that file then add these lines of Passport.js configuration.

const JwtStrategy = require('passport-jwt').Strategy,
    ExtractJwt = require('passport-jwt').ExtractJwt;

// load up the user model
const User = require('../models').User;

module.exports = function(passport) {
  const opts = {
    jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('JWT'),
    secretOrKey: 'nodeauthsecret',
  };
  passport.use('jwt', new JwtStrategy(opts, function(jwt_payload, done) {
    User
      .findByPk(jwt_payload.id)
      .then((user) => { return done(null, user); })
      .catch((error) => { return done(error, false); });
  }));
};

Next, to encrypt the password using Bcrypt and compare the encrypted password with a plain password, open and edit models/user.js then add these functions or methods before the 'return User;' line.

  User.beforeSave(async (user, options) => {
    if (user.password) {
      user.password = bcrypt.hashSync(user.password, bcrypt.genSaltSync(10), null);
    }
  });
  User.prototype.comparePassword = function (passw, cb) {
    bcrypt.compare(passw, this.password, function (err, isMatch) {
        if (err) {
            return cb(err);
        }
        cb(null, isMatch);
    });
  };

Now, the authentication using Passport.js is ready to use in this Node Express REST API application. 


5. Add Permissions Checker

This time to implement the main topics for this tutorial. We need to add a permissions checker function that will be called every time the endpoint is accessed. For that, create a new folder and javascript file.

mkdir utils
touch utils/helper.js

Open that file then add these imports.

const RolePermission = require('../models').RolePermission;
const Permission = require('../models').Permission;

Next, create a Helper class with a method that will check if the permissions exist for the role owned by the logged-in user. If permission is not found, this method will return the forbidden message.

class Helper {
    constructor() {}

    checkPermission(roleId, permName) {
        return new Promise(
            (resolve, reject) => {
                Permission.findOne({
                    where: {
                        perm_name: permName
                    }
                }).then((perm) => {
                    RolePermission.findOne({
                        where: {
                            role_id: roleId,
                            perm_id: perm.id
                        }
                    }).then((rolePermission) => {
                        // console.log(rolePermission);
                        if(rolePermission) {
                            resolve(rolePermission);
                        } else {
                            reject({message: 'Forbidden'});
                        }
                    }).catch((error) => {
                        reject(error);
                    });
                }).catch(() => {
                    reject({message: 'Forbidden'});
                });
            }
        );
    }
}

module.exports = Helper;


6. Create Routers for User, Role, Permissions, and Products CRUD

Because accessing the secure API required permissions based on the role, we need to create CRUD (Create, Read, Update, Delete) endpoints for them. Let's create the Javascript files for Role and Permissions routes.

touch routes/users.js
touch routes/roles.js
touch routes/permissions.js
touch routes/products.js

Next, open and edit routes/users.js then add these lines of routing function that create, get, update and delete user. 

const express = require('express');
const router = express.Router();
const User = require('../models').User;
const Role = require('../models').Role;
const passport = require('passport');
require('../config/passport')(passport);
const Helper = require('../utils/helper');
const helper = new Helper();

// Create a new User
router.post('/', passport.authenticate('jwt', {
  session: false
}), function (req, res) {
  helper.checkPermission(req.user.role_id, 'user_add').then((rolePerm) => {
    if (!req.body.role_id || !req.body.email || !req.body.password || !req.body.fullname || !req.body.phone) {
      res.status(400).send({
        msg: 'Please pass Role ID, email, password, phone or fullname.'
      })
    } else {
      User
        .create({
          email: req.body.email,
          password: req.body.password,
          fullname: req.body.fullname,
          phone: req.body.phone,
          role_id: req.body.role_id
        })
        .then((user) => res.status(201).send(user))
        .catch((error) => {
          console.log(error);
          res.status(400).send(error);
        });
    }
  }).catch((error) => {
    res.status(403).send(error);
  });
});

// Get List of Users
router.get('/', passport.authenticate('jwt', {
  session: false
}), function (req, res) {
  helper.checkPermission(req.user.role_id, 'user_get_all').then((rolePerm) => {
    User
      .findAll()
      .then((users) => res.status(200).send(users))
      .catch((error) => {
        res.status(400).send(error);
      });
  }).catch((error) => {
    res.status(403).send(error);
  });
});

// Get User by ID
router.get('/:id', passport.authenticate('jwt', {
  session: false
}), function (req, res) {
  helper.checkPermission(req.user.role_id, 'user_get').then((rolePerm) => {
    User
      .findByPk(req.params.id)
      .then((user) => res.status(200).send(user))
      .catch((error) => {
        res.status(400).send(error);
      });
  }).catch((error) => {
    res.status(403).send(error);
  });
});

// Update a User
router.put('/:id', passport.authenticate('jwt', {
  session: false
}), function (req, res) {
  helper.checkPermission(req.user.role_id, 'role_update').then((rolePerm) => {
    if (!req.body.role_id || !req.body.email || !req.body.password || !req.body.fullname || !req.body.phone) {
      res.status(400).send({
        msg: 'Please pass Role ID, email, password, phone or fullname.'
      })
    } else {
      User
        .findByPk(req.params.id)
        .then((user) => {
          User.update({
            email: req.body.email || user.email,
            password: req.body.password || user.password,
            fullname: req.body.fullname || user.fullname,
            phone: req.body.phone || user.phone,
            role_id: req.body.role_id || user.role_id
          }, {
            where: {
              id: req.params.id
            }
          }).then(_ => {
            res.status(200).send({
              'message': 'User updated'
            });
          }).catch(err => res.status(400).send(err));
        })
        .catch((error) => {
          res.status(400).send(error);
        });
    }
  }).catch((error) => {
    res.status(403).send(error);
  });
});

// Delete a User
router.delete('/:id', passport.authenticate('jwt', {
  session: false
}), function (req, res) {
  helper.checkPermission(req.user.role_id, 'role_delete').then((rolePerm) => {
    if (!req.params.id) {
      res.status(400).send({
        msg: 'Please pass user ID.'
      })
    } else {
      User
        .findByPk(req.params.id)
        .then((user) => {
          if (user) {
            User.destroy({
              where: {
                id: req.params.id
              }
            }).then(_ => {
              res.status(200).send({
                'message': 'User deleted'
              });
            }).catch(err => res.status(400).send(err));
          } else {
            res.status(404).send({
              'message': 'User not found'
            });
          }
        })
        .catch((error) => {
          res.status(400).send(error);
        });
    }
  }).catch((error) => {
    res.status(403).send(error);
  });
});

module.exports = router;

As you see, each function or endpoint will check for the JWT token from the request headers by adding the passport.authenticate('jwt') function. Also, add the permission checker before the main operation of each endpoint or method. The parameters of the permissions checker are the currently logged-in user role ID (req.user.role_id) and the string value (e.g. 'user_add') that will be added later to the database.

Next, open and edit routes/roles.js then add these lines of codes that contain endpoint for the Roles CRUD operation.

const express = require('express');
const router = express.Router();
const User = require('../models').User;
const Role = require('../models').Role;
const Permission = require('../models').Permission;
const passport = require('passport');
require('../config/passport')(passport);
const Helper = require('../utils/helper');
const helper = new Helper();

// Create a new Role
router.post('/', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'role_add').then((rolePerm) => {
        if (!req.body.role_name || !req.body.role_description) {
            res.status(400).send({
                msg: 'Please pass Role name or description.'
            })
        } else {
            Role
                .create({
                    role_name: req.body.role_name,
                    role_description: req.body.role_description
                })
                .then((role) => res.status(201).send(role))
                .catch((error) => {
                    console.log(error);
                    res.status(400).send(error);
                });
        }
    }).catch((error) => {
        res.status(403).send(error);
    });
});

// Get List of Roles
router.get('/', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'role_get_all').then((rolePerm) => {
        console.log(rolePerm);
        Role
            .findAll({
                include: [
                    {
                        model: Permission,
                        as: 'permissions',
                    },
                    {
                        model: User,
                        as: 'users',
                    }
                ]
            })
            .then((roles) => res.status(200).send(roles))
            .catch((error) => {
                res.status(400).send(error);
            });
    }).catch((error) => {
        res.status(403).send(error);
    });
});

// Get Role by ID
router.get('/:id', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'role_get').then((rolePerm) => {

    }).catch((error) => {
        res.status(403).send(error);
    });
    Role
        .findByPk(
            req.params.id, {
                include: {
                    model: Permission,
                    as: 'permissions',
                }
            }
        )
        .then((roles) => res.status(200).send(roles))
        .catch((error) => {
            res.status(400).send(error);
        });
});

// Update a Role
router.put('/:id', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'role_update').then((rolePerm) => {
        if (!req.params.id || !req.body.role_name || !req.body.role_description) {
            res.status(400).send({
                msg: 'Please pass Role ID, name or description.'
            })
        } else {
            Role
                .findByPk(req.params.id)
                .then((role) => {
                    Role.update({
                        role_name: req.body.role_name || role.role_name,
                        role_description: req.body.role_description || role.role_description
                    }, {
                        where: {
                            id: req.params.id
                        }
                    }).then(_ => {
                        res.status(200).send({
                            'message': 'Role updated'
                        });
                    }).catch(err => res.status(400).send(err));
                })
                .catch((error) => {
                    res.status(400).send(error);
                });
        }
    }).catch((error) => {
        res.status(403).send(error);
    });
});

// Delete a Role
router.delete('/:id', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'role_delete').then((rolePerm) => {
        if (!req.params.id) {
            res.status(400).send({
                msg: 'Please pass role ID.'
            })
        } else {
            Role
                .findByPk(req.params.id)
                .then((role) => {
                    if (role) {
                        Role.destroy({
                            where: {
                                id: req.params.id
                            }
                        }).then(_ => {
                            res.status(200).send({
                                'message': 'Role deleted'
                            });
                        }).catch(err => res.status(400).send(err));
                    } else {
                        res.status(404).send({
                            'message': 'Role not found'
                        });
                    }
                })
                .catch((error) => {
                    res.status(400).send(error);
                });
        }
    }).catch((error) => {
        res.status(403).send(error);
    });
});

// Add Permissions to Role
router.post('/permissions/:id', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'role_add').then((rolePerm) => {
        if (!req.body.permissions) {
            res.status(400).send({
                msg: 'Please pass permissions.'
            })
        } else {
            Role
                .findByPk(req.params.id)
                .then((role) => {
                    req.body.permissions.forEach(function (item, index) {
                        Permission
                            .findByPk(item)
                            .then(async (perm) => {
                                await role.addPermissions(perm, {
                                    through: {
                                        selfGranted: false
                                    }
                                });
                            })
                            .catch((error) => {
                                res.status(400).send(error);
                            });
                    });
                    res.status(200).send({
                        'message': 'Permissions added'
                    });
                })
                .catch((error) => {
                    res.status(400).send(error);
                });
        }
    }).catch((error) => {
        res.status(403).send(error);
    });
});

module.exports = router;

As you see, there's also an endpoint to add permissions to a role. Next, open and edit routes/permissions.js then add these lines of codes that contain endpoint for the Permissions CRUD operation.

const express = require('express');
const router = express.Router();
const Permission = require('../models').Permission;
const passport = require('passport');
require('../config/passport')(passport);
const Helper = require('../utils/helper');
const helper = new Helper();

// Create a new permission
router.post('/', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'permissions_add').then((rolePerm) => {
        if (!req.body.perm_name || !req.body.perm_description) {
            res.status(400).send({
                msg: 'Please pass permission name or description.'
            })
        } else {
            Permission
                .create({
                    perm_name: req.body.perm_name,
                    perm_description: req.body.perm_description
                })
                .then((perm) => res.status(201).send(perm))
                .catch((error) => {
                    console.log(error);
                    res.status(400).send(error);
                });
        }
    }).catch((error) => {
        res.status(403).send(error);
    });
});

// Get List of permissions
router.get('/', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'permissions_get_all').then((rolePerm) => {
        Permission
            .findAll()
            .then((perms) => res.status(200).send(perms))
            .catch((error) => {
                res.status(400).send(error);
            });
    }).catch((error) => {
        res.status(403).send(error);
    });
});

// Update a permission
router.put('/:id', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'permissions_update').then((rolePerm) => {
        if (!req.params.id || !req.body.perm_name || !req.body.perm_description) {
            res.status(400).send({
                msg: 'Please pass permission ID, name or description.'
            })
        } else {
            Permission
                .findByPk(req.params.id)
                .then((perm) => {
                    Permission.update({
                        perm_name: req.body.perm_name || perm.perm_name,
                        perm_description: req.body.perm_description || perm.perm_description
                    }, {
                        where: {
                            id: req.params.id
                        }
                    }).then(_ => {
                        res.status(200).send({
                            'message': 'permission updated'
                        });
                    }).catch(err => res.status(400).send(err));
                })
                .catch((error) => {
                    res.status(400).send(error);
                });
        }
    }).catch((error) => {
        res.status(403).send(error);
    });
});

// Delete a permission
router.delete('/:id', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'permissions_delete').then((rolePerm) => {
        if (!req.params.id) {
            res.status(400).send({
                msg: 'Please pass permission ID.'
            })
        } else {
            Permission
                .findByPk(req.params.id)
                .then((perm) => {
                    if (perm) {
                        perm.destroy({
                            where: {
                                id: req.params.id
                            }
                        }).then(_ => {
                            res.status(200).send({
                                'message': 'permission deleted'
                            });
                        }).catch(err => res.status(400).send(err));
                    } else {
                        res.status(404).send({
                            'message': 'permission not found'
                        });
                    }
                })
                .catch((error) => {
                    res.status(400).send(error);
                });
        }
    }).catch((error) => {
        res.status(403).send(error);
    });
});

module.exports = router;

Next, open and edit routes/products.js then add these lines of codes that contain endpoint for the Products CRUD operation.

const express = require('express');
const router = express.Router();
const Product = require('../models').Product;
const passport = require('passport');
require('../config/passport')(passport);
const Helper = require('../utils/helper');
const helper = new Helper();

// Create a new Product
router.post('/', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'product_add').then((rolePerm) => {
        if (!req.body.prod_name || !req.body.prod_description || !req.body.prod_image || !req.body.prod_price) {
            res.status(400).send({
                msg: 'Please pass Product name, description, image or price.'
            })
        } else {
            Product
                .create({
                    prod_name: req.body.prod_name,
                    prod_description: req.body.prod_description,
                    prod_image: req.body.prod_image,
                    prod_price: req.body.prod_price
                })
                .then((product) => res.status(201).send(product))
                .catch((error) => {
                    console.log(error);
                    res.status(400).send(error);
                });
        }
    }).catch((error) => {
        res.status(403).send(error);
    });
});

// Get List of Products
router.get('/', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'product_get_all').then((rolePerm) => {
        Product
            .findAll()
            .then((products) => res.status(200).send(products))
            .catch((error) => {
                res.status(400).send(error);
            });
    }).catch((error) => {
        res.status(403).send(error);
    });
});

// Get Product by ID
router.get('/:id', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'product_get').then((rolePerm) => {
        Product
            .findByPk(req.params.id)
            .then((product) => res.status(200).send(product))
            .catch((error) => {
                res.status(400).send(error);
            });
    }).catch((error) => {
        res.status(403).send(error);
    });
});

// Update a Product
router.put('/:id', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'product_update').then((rolePerm) => {
        if (!req.body.prod_name || !req.body.prod_description || !req.body.prod_image || !req.body.prod_price) {
            res.status(400).send({
                msg: 'Please pass Product name, description, image or price.'
            })
        } else {
            Product
                .findByPk(req.params.id)
                .then((product) => {
                    Product.update({
                        prod_name: req.body.prod_name || user.prod_name,
                        prod_description: req.body.prod_description || user.prod_description,
                        prod_image: req.body.prod_image || user.prod_image,
                        prod_price: req.body.prod_price || user.prod_price
                    }, {
                        where: {
                            id: req.params.id
                        }
                    }).then(_ => {
                        res.status(200).send({
                            'message': 'Product updated'
                        });
                    }).catch(err => res.status(400).send(err));
                })
                .catch((error) => {
                    res.status(400).send(error);
                });
        }
    }).catch((error) => {
        res.status(403).send(error);
    });
});

// Delete a Product
router.delete('/:id', passport.authenticate('jwt', {
    session: false
}), function (req, res) {
    helper.checkPermission(req.user.role_id, 'product_delete').then((rolePerm) => {
        if (!req.params.id) {
            res.status(400).send({
                msg: 'Please pass product ID.'
            })
        } else {
            Product
                .findByPk(req.params.id)
                .then((product) => {
                    if (product) {
                        Product.destroy({
                            where: {
                                id: req.params.id
                            }
                        }).then(_ => {
                            res.status(200).send({
                                'message': 'Product deleted'
                            });
                        }).catch(err => res.status(400).send(err));
                    } else {
                        res.status(404).send({
                            'message': 'Product not found'
                        });
                    }
                })
                .catch((error) => {
                    res.status(400).send(error);
                });
        }
    }).catch((error) => {
        res.status(403).send(error);
    });
});

module.exports = router;


7. Create Routers for Authentication

This router just has signup and signing endpoints. So, we just need to create a new route file in the routes folder.

touch routes/auth.js

Open and edit that file then these lines of Javascript codes that contain endpoint for sign in and signup.

As you see, all endpoints not using the passport authenticate and checkPermission methods because those endpoints will be used by the public. For the sign-in method, we use a simple user finder then password validation after the user is found. We use the default 'admin' Role for the new user signup. For other roles and users, will be created by the admin user using the roles and users endpoint.

const express = require('express');
const jwt = require('jsonwebtoken');
const passport = require('passport');
const router = express.Router();
require('../config/passport')(passport);
const User = require('../models').User;
const Role = require('../models').Role;

router.post('/signup', function (req, res) {
    if (!req.body.email || !req.body.password || !req.body.fullname) {
        res.status(400).send({
            msg: 'Please pass username, password and name.'
        })
    } else {
        Role.findOne({
            where: {
                role_name: 'admin'
            }
        }).then((role) => {
            console.log(role.id);
            User
            .create({
                email: req.body.email,
                password: req.body.password,
                fullname: req.body.fullname,
                phone: req.body.phone,
                role_id: role.id
            })
            .then((user) => res.status(201).send(user))
            .catch((error) => {
                res.status(400).send(error);
            });
        }).catch((error) => {
            res.status(400).send(error);
        });
    }
});

router.post('/signin', function (req, res) {
    User
        .findOne({
            where: {
                email: req.body.email
            }
        })
        .then((user) => {
            if (!user) {
                return res.status(401).send({
                    message: 'Authentication failed. User not found.',
                });
            }
            user.comparePassword(req.body.password, (err, isMatch) => {
                if (isMatch && !err) {
                    var token = jwt.sign(JSON.parse(JSON.stringify(user)), 'nodeauthsecret', {
                        expiresIn: 86400 * 30
                    });
                    jwt.verify(token, 'nodeauthsecret', function (err, data) {
                        console.log(err, data);
                    })
                    res.json({
                        success: true,
                        token: 'JWT ' + token
                    });
                } else {
                    res.status(401).send({
                        success: false,
                        msg: 'Authentication failed. Wrong password.'
                    });
                }
            })
        })
        .catch((error) => res.status(400).send(error));
});

module.exports = router;


8. Run and Test Authentication Role Permission API using Node Express MySQL

To run and see how this whole Node/Express application working, we need to prepare a few things. First, make sure MySQL/MariaDB is running then you can export the initial data for Role and Permissions. You can find the SQL file in the SQL folder.

Next, run the Node application by type this command.

nodemon

Open the Postman application to check or test each endpoint. Don't worry, you can import the Postman collections in the postman folder. Next, you can start test using postman collection started by signup for a new user, sign in, add the token to the environment variables, then you can test the rest of the secured and permissions-based endpoints.

To make sure the user without valid permissions can't access the endpoint, create a new role, add specific permissions to the role, then create a new user using that role.

That it's, the Authentication Role Permission API using Node Express MySQL. You can get the source codes from our GitHub.

That just the basic. If you need more deep learning about Node, Express, Sequelize, PostgreSQL/MySQ or related you can take the following cheap course:

Thanks!