Node.js Token Based Authentication & Authorization example
Overview of Node.js JWT Authentication example
We will build a Node.js Express application in that:
User can signup new account, or login with username & password.
By User’s role (admin, moderator, user), we authorize the User to access resources
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
The diagram shows flow of User Registration, User Login and Authorization process.

A legal JWT must be added to HTTP x-access-token Header if Client accesses protected resources.
Node.js Express Architecture with Authentication & Authorization
You can have an overview of our Node.js Express App with the diagram below:

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
If these middlewares throw any error, a message will be sent as HTTP response.
Controllers interact with MySQL Database via Sequelize 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
Sequelize 5.21.3
MySQL
Project Structure
This is directory structure for our Node.js Express application:

Create Node.js App
First, we create a folder for our project:
$ mkdir node-js-jwt-auth
$ cd node-js-jwt-auth
Then we initialize the Node.js App with a package.json file:
npm init
name: (node-js-jwt-auth)
version: (1.0.0)
description: Node.js Demo for JWT Authentication
entry point: (index.js) server.js
test command:
git repository:
keywords: node.js, express, jwt, authentication, mysql
author: bezkoder
license: (ISC)
Is this ok? (yes) yes
We need to install necessary modules: express
, cors
, body-parser
, sequelize
, mysql2
, jsonwebtoken
and bcryptjs
.
Run the command:
npm install express sequelize mysql2 body-parser cors jsonwebtoken bcryptjs --save
The package.json file now looks like this:
{
"name": "node-js-jwt-auth",
"version": "1.0.0",
"description": "Node.js Demo for JWT Authentication",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"node.js",
"jwt",
"authentication",
"express",
"mysql"
],
"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",
"mysql2": "^2.1.0",
"sequelize": "^5.21.3"
}
}
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}.`);
});
Let me explain what we’ve just done:
– import express
, body-parser
and cors
modules:
Express is for building the Rest apis
body-parser helps to parse the request and create the
req.body
objectcors 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:

Configure MySQL database & Sequelize
In the app folder, create config folder for configuration with db.config.js file like this:
module.exports = {
HOST: "localhost",
USER: "root",
PASSWORD: "123456",
DB: "testdb",
dialect: "mysql",
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
}
};
First five parameters are for MySQL connection.
pool
is optional, it will be used for Sequelize connection pool configuration:
max
: maximum number of connection in poolmin
: minimum number of connection in poolidle
: maximum time, in milliseconds, that a connection can be idle before being releasedacquire
: maximum time, in milliseconds, that pool will try to get connection before throwing error
For more details, please visit API Reference for the Sequelize constructor.
Define the Sequelize Model
In models folder, create User
and Role
data model as following code:
models/user.model.js
module.exports = (sequelize, Sequelize) => {
const User = sequelize.define("users", {
username: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
}
});
return User;
};
models/role.model.js
module.exports = (sequelize, Sequelize) => {
const Role = sequelize.define("roles", {
id: {
type: Sequelize.INTEGER,
primaryKey: true
},
name: {
type: Sequelize.STRING
}
});
return Role;
};
These Sequelize Models represents users & roles table in MySQL database.
After initializing Sequelize, we don’t need to write CRUD functions, Sequelize supports all of them:
create a new User:
create
(object)
find a User by id:
findByPk
(id)
find a User by email:
findOne
({ where: { email: ... } })
get all Users:
findAll
()
find all Users by username:
findAll({ where: { username: ... } })
These functions will be used in our Controllers and Middlewares.
Initialize Sequelize
Now create app/models/index.js with content like this:
const config = require("../config/db.config.js");
const Sequelize = require("sequelize");
const sequelize = new Sequelize(
config.DB,
config.USER,
config.PASSWORD,
{
host: config.HOST,
dialect: config.dialect,
operatorsAliases: false,
pool: {
max: config.pool.max,
min: config.pool.min,
acquire: config.pool.acquire,
idle: config.pool.idle
}
}
);
const db = {};
db.Sequelize = Sequelize;
db.sequelize = sequelize;
db.user = require("../models/user.model.js")(sequelize, Sequelize);
db.role = require("../models/role.model.js")(sequelize, Sequelize);
db.role.belongsToMany(db.user, {
through: "user_roles",
foreignKey: "roleId",
otherKey: "userId"
});
db.user.belongsToMany(db.role, {
through: "user_roles",
foreignKey: "userId",
otherKey: "roleId"
});
db.ROLES = ["user", "admin", "moderator"];
module.exports = db;
The association between Users and Roles is Many-to-Many relationship: – One User can have several Roles. – One Role can be taken on by many Users.
We use User.belongsToMany(Role)
to indicate that the user model can belong to many Roles and vice versa.
With through, foreignKey, otherKey
, we’re gonna have a new table user_roles as connection between users and roles table via their primary key as foreign keys.
If you want to know more details about how to make Many-to-Many Association with Sequelize and Node.js, please visit: Sequelize Many-to-Many Association example – Node.js & MySQL
Don’t forget to call sync()
method in server.js.
...
const app = express();
app.use(...);
const db = require("./app/models");
const Role = db.role;
db.sequelize.sync({force: true}).then(() => {
console.log('Drop and Resync Db');
initial();
});
...
function initial() {
Role.create({
id: 1,
name: "user"
});
Role.create({
id: 2,
name: "moderator"
});
Role.create({
id: 3,
name: "admin"
});
}
initial()
function helps us to create 3 rows in database.
In development, you may need to drop existing tables and re-sync database. So you can use force: true
as code above.
For production, just insert these rows manually and use sync()
without parameters to avoid dropping data:
...
const app = express();
app.use(...);
const db = require("./app/models");
db.sequelize.sync();
...
Learn how to implement Sequelize One-to-Many Relationship at: Sequelize Associations: One-to-Many example – Node.js, MySQL
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 if username
or email
is duplicate or not
– check if roles
in the request is existed or not
middlewares/verifySignUp.js
const db = require("../models");
const ROLES = db.ROLES;
const User = db.user;
checkDuplicateUsernameOrEmail = (req, res, next) => {
// Username
User.findOne({
where: {
username: req.body.username
}
}).then(user => {
if (user) {
res.status(400).send({
message: "Failed! Username is already in use!"
});
return;
}
// Email
User.findOne({
where: {
email: req.body.email
}
}).then(user => {
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 does not exist = " + req.body.roles[i]
});
return;
}
}
}
next();
};
const verifySignUp = {
checkDuplicateUsernameOrEmail: checkDuplicateUsernameOrEmail,
checkRolesExisted: checkRolesExisted
};
module.exports = verifySignUp;
To process Authentication & Authorization, we have these 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;
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.findByPk(req.userId).then(user => {
user.getRoles().then(roles => {
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.findByPk(req.userId).then(user => {
user.getRoles().then(roles => {
for (let i = 0; i < roles.length; i++) {
if (roles[i].name === "moderator") {
next();
return;
}
}
res.status(403).send({
message: "Require Moderator Role!"
});
});
});
};
isModeratorOrAdmin = (req, res, next) => {
User.findByPk(req.userId).then(user => {
user.getRoles().then(roles => {
for (let i = 0; i < roles.length; i++) {
if (roles[i].name === "moderator") {
next();
return;
}
if (roles[i].name === "admin") {
next();
return;
}
}
res.status(403).send({
message: "Require Moderator or Admin Role!"
});
});
});
};
const authJwt = {
verifyToken: verifyToken,
isAdmin: isAdmin,
isModerator: isModerator,
isModeratorOrAdmin: isModeratorOrAdmin
};
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 existscompare
password
withpassword
in database using bcrypt, if it is correctgenerate a token using jsonwebtoken
return user information & access Token
controllers/auth.controller.js
const db = require("../models");
const config = require("../config/auth.config");
const User = db.user;
const Role = db.role;
const Op = db.Sequelize.Op;
var jwt = require("jsonwebtoken");
var bcrypt = require("bcryptjs");
exports.signup = (req, res) => {
// Save User to Database
User.create({
username: req.body.username,
email: req.body.email,
password: bcrypt.hashSync(req.body.password, 8)
})
.then(user => {
if (req.body.roles) {
Role.findAll({
where: {
name: {
[Op.or]: req.body.roles
}
}
}).then(roles => {
user.setRoles(roles).then(() => {
res.send({ message: "User was registered successfully!" });
});
});
} else {
// user role = 1
user.setRoles([1]).then(() => {
res.send({ message: "User was registered successfully!" });
});
}
})
.catch(err => {
res.status(500).send({ message: err.message });
});
};
exports.signin = (req, res) => {
User.findOne({
where: {
username: req.body.username
}
})
.then(user => {
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 = [];
user.getRoles().then(roles => {
for (let i = 0; i < roles.length; i++) {
authorities.push("ROLE_" + roles[i].name.toUpperCase());
}
res.status(200).send({
id: user.id,
username: user.username,
email: user.email,
roles: authorities,
accessToken: token
});
});
})
.catch(err => {
res.status(500).send({ message: err.message });
});
};
Controller for testing Authorization
There are 4 functions:
– /api/test/all
for public access
– /api/test/user
for loggedin users (role: user/moderator/admin)
– /api/test/mod
for users having moderator role
– /api/test/admin
for users having admin role
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.");
};
Now, do you have any question? Would you like to know how we can combine middlewares with controller functions? Let's do it 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("../middleware");
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 moderatorGET
/api/test/admin
for admin
routes/user.routes.js
const { authJwt } = require("../middleware");
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
Tables that we define in models package will be automatically generated in MySQL Database. If you check the database, you can see things like this:
mysql> describe users;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(255) | YES | | NULL | |
| email | varchar(255) | YES | | NULL | |
| password | varchar(255) | YES | | NULL | |
| createdAt | datetime | NO | | NULL | |
| updatedAt | datetime | NO | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
mysql> describe roles;
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(255) | YES | | NULL | |
| createdAt | datetime | NO | | NULL | |
| updatedAt | datetime | NO | | NULL | |
+-----------+--------------+------+-----+---------+-------+
mysql> describe user_roles;
+-----------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+----------+------+-----+---------+-------+
| createdAt | datetime | NO | | NULL | |
| updatedAt | datetime | NO | | NULL | |
| roleId | int(11) | NO | PRI | NULL | |
| userId | int(11) | NO | PRI | NULL | |
+-----------+----------+------+-----+---------+-------+
mysql> select * from roles;
+----+-----------+---------------------+---------------------+
| id | name | createdAt | updatedAt |
+----+-----------+---------------------+---------------------+
| 1 | user | 2020-01-13 09:05:39 | 2020-01-13 09:05:39 |
| 2 | moderator | 2020-01-13 09:05:39 | 2020-01-13 09:05:39 |
| 3 | admin | 2020-01-13 09:05:39 | 2020-01-13 09:05:39 |
+----+-----------+---------------------+---------------------+
Register some users with /signup
API:
admin with
admin
rolemod with
moderator
anduser
roleszkoder with
user
role

Our tables after signup could look like this.
mysql> select * from users;
+----+----------+--------------------+--------------------------------------------------------------+---------------------+---------------------+
| id | username | email | password | createdAt | updatedAt |
+----+----------+--------------------+--------------------------------------------------------------+---------------------+---------------------+
| 1 | admin | admin@bezkoder.com | $2a$08$w3cYCF.N0UQZO19z8CQSZ.whzxFS5vMoi9k51g3TQx9r5tkwrIXO2 | 2020-01-13 09:21:51 | 2020-01-13 09:21:51 |
| 2 | mod | mod@bezkoder.com | $2a$08$tTj1l28esAxPSSvl3YqKl./nz35vQF7Y76jGtzcYUhHtGy6d.1/ze | 2020-01-13 09:22:01 | 2020-01-13 09:22:01 |
| 3 | zkoder | user@bezkoder.com | $2a$08$U2F07dLyYZjzTxQbFMCAcOd1k8V1o9f6E4TGVJHpy0V6/DC7iS0CS | 2020-01-13 09:23:25 | 2020-01-13 09:23:25 |
+----+----------+--------------------+--------------------------------------------------------------+---------------------+---------------------+
mysql> select * from user_roles;
+---------------------+---------------------+--------+--------+
| createdAt | updatedAt | roleId | userId |
+---------------------+---------------------+--------+--------+
| 2020-01-13 09:22:01 | 2020-01-13 09:22:01 | 1 | 2 |
| 2020-01-13 09:23:25 | 2020-01-13 09:23:25 | 1 | 3 |
| 2020-01-13 09:22:01 | 2020-01-13 09:22:01 | 2 | 2 |
| 2020-01-13 09:21:51 | 2020-01-13 09:21:51 | 3 | 1 |
+---------------------+---------------------+--------+--------+
Access public resource: GET /api/test/all

Access protected resource: GET /api/test/user

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

Login an account: POST /api/auth/signin

Access protected resources: GET /api/test/user

Last updated
Was this helpful?