“How to implement Token Based Authentication in Node.js using JWT (JSON Web Tokens)?” is one of the most common question in the development world? So in the tutorial, I will introduce details step by step how to do it with 100% running coding example by Express RestAPIs, JWT(JSON Web Token), BCryptjs, Sequelize and MySQL/PostgreSQL database.
Overview – Nodejs JWT Express Security Authentication
Technologies
To implement tutorial: ‘Token Based Authentication in Node.js using JWT (JSON Web Tokens)’, we need a set of below technologies:
- Nodejs/Express
- Json Web Token
- BCryptjs
- Sequelize
- MySQL/PostgreSQL
JSON Web Token
JSON Web Token (JWT) defines a compact and self-contained way for securely transmitting information between parties as a JSON object.
Scenarios where JSON Web Tokens are useful:
Authorization
: the most common scenario for using JWT. Single Sign On is a feature that widely uses JWTInformation Exchange
: Because JWTs can be signed, JSON Web Tokens are a good way of securely transmitting information between parties.
JSON Web Tokens consist of 3 parts:
- Header
- Payload
- Signature
JWT
looks like Header-Base64-String.Payload-Base64-String.Signature-Base64-String
Header consists of two parts:
- token type.
- hashing algorithm.
Example:
{
"alg": "HS256",
"typ": "JWT"
}
Payload contains the claims. Claims are statements about an entity and additional information.
There are 3 types of claims:
Registered claims
: These are a set of predefined claims:iss
(issuer),exp
(expiration time),sub
(subject)Public claims
Private claims
Example :
{
"id": 3,
"iat": 1538339534,
"exp": 1538425934
}
Signature
: To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.
Example:
HMACSHA512(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
Combine all together, we get 3 Base64-URL strings separated by dots,
Example:
– Encoded
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaWF0IjoxNTM4MzM5NTM0LCJleHAiOjE1Mzg0MjU5MzR9.wKse6-ERNP4g_sPBdM72GZgpNpHH87UGbzYH3_0mdpo
– Decoded

Project Structure

– config
package defines MySQL/PostgreSQL Database Configuration, JWT Secret Key & User Roles.
– model
package defines Role
& User
Sequelize models.

– router
package defines RestAPI URLs, verification functions for signup, & verification JWT token – function for signin.
– controller
package defines proccesing functions for each RestAPIs declared in router package.
Workflow – Token Based Authentication in Node.js using JWT (JSON Web Tokens)
We define 5 workflows as below:
- SignUp Scenarios
- SignIn Scenarios
- Access User Content
- Access PM Content
- Access Admin Content
– SignUp Scenarios:
-> Verify UserName & Email -> If NOT Duplicate (UserName & Email), verify Roles are existed. -> If Roles are available, save User Info to database by Sequlieze ORM -> Othercase, Eror code will be returned
Corresponding code in router.js
:
app.post('/api/auth/signup', [verifySignUp.checkDuplicateUserNameOrEmail, verifySignUp.checkRolesExisted], controller.signup);
– SignIn Scenarios:
-> Find User record in database by username -> If User is existed, check password is Valid or NOT -> If password is valid, create JWT then return JWT token back to client -> Othercase, Error code will be returned
Corresponding code in router.js
:
app.post('/api/auth/signin', controller.signin);
– Access User Content:
-> Verify JWT Token
-> If token is valid, controller
will load & return User Info back to client
-> Othercase, Error Code will be returned
Corressponding code in router.js
app.get('/api/test/user', [authJwt.verifyToken], controller.userContent);
– Access PM Content
-> Verify JWT Token -> If token is valid, verifyPM
role. -> If User has Admin or PM role,controller
will load & return Management Content to client. -> Othercase, Error code will be returned
Corresponding code in router.js
:
app.get('/api/test/pm', [authJwt.verifyToken, authJwt.isPmOrAdmin], controller.managementBoard);
– Access Admin Content:
-> Verify JWT Token -> If token is valid, verifyADMIN
role. -> If User has Admin role,controller
will load & return Admin Content to client. -> Othercase, Error code will be returned
Corresponding code in router.js
:
app.get('/api/test/admin', [authJwt.verifyToken, authJwt.isAdmin], controller.adminBoard);
Goal
– Sign Up:

– Sign In

– Access API Successfully

– Unauthorized Access

Video Guide – Nodejs JWT Express Security Authentication
Practice – How to do it by Coding?
Create Nodejs Project
– Install Express, Sequelize, MySQL, Json Web Token, Bcryptjs:
$npm install express sequelize mysql2 jsonwebtoken bcryptjs --save
package.json
file:
{
"name": "nodejs-jwt-auth",
"version": "1.0.0",
"description": "Build Nodejs JWT Authentication RestAPIs ",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"nodejs",
"jwt",
"authentication",
"restapis"
],
"author": "https://loizenai.com",
"license": "ISC",
"dependencies": {
"bcryptjs": "^2.4.3",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"mysql2": "^2.1.0",
"sequelize": "^5.21.12"
}
}
Create Sequelize Models
– User
model:
module.exports = (sequelize, Sequelize) => {
const User = sequelize.define('users', {
name: {
type: Sequelize.STRING
},
username: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
}
});
return User;
}
– Role
model:
module.exports = (sequelize, Sequelize) => {
const Role = sequelize.define('roles', {
id: {
type: Sequelize.INTEGER,
primaryKey: true
},
name: {
type: Sequelize.STRING
}
});
return Role;
}
Sequelize Database Configuration
– /app/config/env.js
file :
For MySQL database
const env = {
database: 'loizenaidb',
username: 'root',
password: '12345',
host: 'localhost',
dialect: 'mysql',
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
}
};
module.exports = env;
For PostgreSQL database
const env = {
database: 'loizenai',
username: 'postgres',
password: '123',
host: 'localhost',
dialect: 'postgres',
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
}
};
module.exports = env;
/app/config/db.config.js
:
const env = require('./env.js');
const Sequelize = require('sequelize');
const sequelize = new Sequelize(env.database, env.username, env.password, {
host: env.host,
dialect: env.dialect,
operatorsAliases: false,
pool: {
max: env.max,
min: env.pool.min,
acquire: env.pool.acquire,
idle: env.pool.idle
}
});
const db = {};
db.Sequelize = Sequelize;
db.sequelize = sequelize;
db.user = require('../model/user.model.js')(sequelize, Sequelize);
db.role = require('../model/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'});
module.exports = db;
Because Role
& User
has many-to-many association, so we use belongsToMany
to configure them.
Define RestAPIs Router
We define 5 RestAPIs in /app/router/router.js
const verifySignUp = require('./verifySignUp');
const authJwt = require('./verifyJwtToken');
module.exports = function(app) {
const controller = require('../controller/controller.js');
app.post('/api/auth/signup', [verifySignUp.checkDuplicateUserNameOrEmail, verifySignUp.checkRolesExisted], controller.signup);
app.post('/api/auth/signin', controller.signin);
app.get('/api/test/user', [authJwt.verifyToken], controller.userContent);
app.get('/api/test/pm', [authJwt.verifyToken, authJwt.isPmOrAdmin], controller.managementBoard);
app.get('/api/test/admin', [authJwt.verifyToken, authJwt.isAdmin], controller.adminBoard);
}
We need implement middleware functions to do a verification for SignUp & SignIn:
– /app/router/verifySignUp.js
implements 2 middleware functions:
checkDuplicateUserNameOrEmail
: checking the posted username or email is duplicated or NOTcheckRolesExisted
-> checking the posted User Role is existed or NOT
const db = require('../config/db.config.js');
const config = require('../config/config.js');
const ROLEs = config.ROLEs;
const User = db.user;
const Role = db.role;
checkDuplicateUserNameOrEmail = (req, res, next) => {
// -> Check Username is already in use
User.findOne({
where: {
username: req.body.username
}
}).then(user => {
if(user){
res.status(400).send("Fail -> Username is already taken!");
return;
}
// -> Check Email is already in use
User.findOne({
where: {
email: req.body.email
}
}).then(user => {
if(user){
res.status(400).send("Fail -> Email is already in use!");
return;
}
next();
});
});
}
checkRolesExisted = (req, res, next) => {
for(let i=0; i<req.body.roles.length; i++){
if(!ROLEs.includes(req.body.roles[i].toUpperCase())){
res.status(400).send("Fail -> Does NOT exist Role = " + req.body.roles[i]);
return;
}
}
next();
}
const signUpVerify = {};
signUpVerify.checkDuplicateUserNameOrEmail = checkDuplicateUserNameOrEmail;
signUpVerify.checkRolesExisted = checkRolesExisted;
module.exports = signUpVerify;
– /app/router/verifyJwtToken.js
implements 3 middleware functions:
verifyToken
: checking a JWT token is valid or NOTisAdmin
: checking an User has ADMIN role or NOTisPmOrAdmin
: checking an User has PM or ADMIN role or NOT
const jwt = require('jsonwebtoken');
const config = require('../config/config.js');
const db = require('../config/db.config.js');
const Role = db.role;
const User = db.user;
verifyToken = (req, res, next) => {
let token = req.headers['x-access-token'];
if (!token){
return res.status(403).send({
auth: false, message: 'No token provided.'
});
}
jwt.verify(token, config.secret, (err, decoded) => {
if (err){
return res.status(500).send({
auth: false,
message: 'Fail to Authentication. Error -> ' + err
});
}
req.userId = decoded.id;
next();
});
}
isAdmin = (req, res, next) => {
let token = req.headers['x-access-token'];
User.findByPk(req.userId)
.then(user => {
user.getRoles().then(roles => {
for(let i=0; i<roles.length; i++){
console.log(roles[i].name);
if(roles[i].name.toUpperCase() === "ADMIN"){
next();
return;
}
}
res.status(403).send("Require Admin Role!");
return;
})
})
}
isPmOrAdmin = (req, res, next) => {
let token = req.headers['x-access-token'];
User.findByPk(req.userId)
.then(user => {
user.getRoles().then(roles => {
for(let i=0; i<roles.length; i++){
if(roles[i].name.toUpperCase() === "PM"){
next();
return;
}
if(roles[i].name.toUpperCase() === "ADMIN"){
next();
return;
}
}
res.status(403).send("Require PM or Admin Roles!");
})
})
}
const authJwt = {};
authJwt.verifyToken = verifyToken;
authJwt.isAdmin = isAdmin;
authJwt.isPmOrAdmin = isPmOrAdmin;
module.exports = authJwt;
Implement Express Controller
– /app/controller/controller.js
exports 5 funtions:
signup
: be used to register new Usersignin
: be used to LoginuserContent
: get User InfomanagementBoard
: get Management Board ContentadminBoard
: get Admin Board Content
const db = require('../config/db.config.js');
const config = require('../config/config.js');
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
console.log("Processing func -> SignUp");
User.create({
name: req.body.name,
username: req.body.username,
email: req.body.email,
password: bcrypt.hashSync(req.body.password, 8)
}).then(user => {
Role.findAll({
where: {
name: {
[Op.or]: req.body.roles
}
}
}).then(roles => {
user.setRoles(roles).then(() => {
res.send("User registered successfully!");
});
}).catch(err => {
res.status(500).send("Error -> " + err);
});
}).catch(err => {
res.status(500).send("Fail! Error -> " + err);
})
}
exports.signin = (req, res) => {
console.log("Sign-In");
User.findOne({
where: {
username: req.body.username
}
}).then(user => {
if (!user) {
return res.status(404).send('User Not Found.');
}
var passwordIsValid = bcrypt.compareSync(req.body.password, user.password);
if (!passwordIsValid) {
return res.status(401).send({ auth: false, accessToken: null, reason: "Invalid Password!" });
}
var token = jwt.sign({ id: user.id }, config.secret, {
expiresIn: 86400 // expires in 24 hours
});
res.status(200).send({ auth: true, accessToken: token });
}).catch(err => {
res.status(500).send('Error -> ' + err);
});
}
exports.userContent = (req, res) => {
User.findOne({
where: {id: req.userId},
attributes: ['name', 'username', 'email'],
include: [{
model: Role,
attributes: ['id', 'name'],
through: {
attributes: ['userId', 'roleId'],
}
}]
}).then(user => {
res.status(200).json({
"description": "User Content Page",
"user": user
});
}).catch(err => {
res.status(500).json({
"description": "Can not access User Page",
"error": err
});
})
}
exports.adminBoard = (req, res) => {
User.findOne({
where: {id: req.userId},
attributes: ['name', 'username', 'email'],
include: [{
model: Role,
attributes: ['id', 'name'],
through: {
attributes: ['userId', 'roleId'],
}
}]
}).then(user => {
res.status(200).json({
"description": "Admin Board",
"user": user
});
}).catch(err => {
res.status(500).json({
"description": "Can not access Admin Board",
"error": err
});
})
}
exports.managementBoard = (req, res) => {
User.findOne({
where: {id: req.userId},
attributes: ['name', 'username', 'email'],
include: [{
model: Role,
attributes: ['id', 'name'],
through: {
attributes: ['userId', 'roleId'],
}
}]
}).then(user => {
res.status(200).json({
"description": "Management Board",
"user": user
});
}).catch(err => {
res.status(500).json({
"description": "Can not access Management Board",
"error": err
});
})
}
– Create /app/config/config.js
file that defines jwt-secret-key
& User
Roles.
module.exports = {
'secret': 'grokonez-super-secret-key',
ROLEs: ['USER', 'ADMIN', 'PM']
};
Nodejs Server
– /app/server.js
file :
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.json())
require('./app/router/router.js')(app);
const db = require('./app/config/db.config.js');
const Role = db.role;
// force: true will drop the table if it already exists
db.sequelize.sync({force: true}).then(() => {
console.log('Drop and Resync with { force: true }');
initial();
});
//require('./app/route/project.route.js')(app);
// Create a Server
var server = app.listen(8080, function () {
var host = server.address().address
var port = server.address().port
console.log("App listening at http://%s:%s", host, port)
})
function initial(){
Role.create({
id: 1,
name: "USER"
});
Role.create({
id: 2,
name: "ADMIN"
});
Role.create({
id: 3,
name: "PM"
});
}
Run & Check Results
Start Nodejs Server
– Run Nodejs server by cmd npm start
-> Logs:
D:\loizenai.com\nodejs-jwt-auth>npm start
> nodejs-jwt-auth@1.0.0 start D:\loizenai.com\nodejs-jwt-auth
> node server.js
(node:6948) [SEQUELIZE0004] DeprecationWarning: A boolean value was passed to options.operatorsAliases. This is a no-op
with v5 and should be removed.
App listening at http://:::8080
Executing (default): DROP TABLE IF EXISTS `user_roles`;
Executing (default): DROP TABLE IF EXISTS `roles`;
Executing (default): DROP TABLE IF EXISTS `users`;
Executing (default): DROP TABLE IF EXISTS `users`;
Executing (default): CREATE TABLE IF NOT EXISTS `users` (`id` INTEGER NOT NULL auto_increment , `name` VARCHAR(255), `us
ername` VARCHAR(255), `email` VARCHAR(255), `password` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME
NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `users`
Executing (default): DROP TABLE IF EXISTS `roles`;
Executing (default): CREATE TABLE IF NOT EXISTS `roles` (`id` INTEGER , `name` VARCHAR(255), `createdAt` DATETIME NOT NU
LL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `roles`
Executing (default): DROP TABLE IF EXISTS `user_roles`;
Executing (default): CREATE TABLE IF NOT EXISTS `user_roles` (`createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NU
LL, `roleId` INTEGER , `userId` INTEGER , PRIMARY KEY (`roleId`, `userId`), FOREIGN KEY (`roleId`) REFERENCES `roles` (`
id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE C
ASCADE) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `user_roles`
Drop and Resync with { force: true }
Executing (default): INSERT INTO `roles` (`id`,`name`,`createdAt`,`updatedAt`) VALUES (?,?,?,?);
Executing (default): INSERT INTO `roles` (`id`,`name`,`createdAt`,`updatedAt`) VALUES (?,?,?,?);
Executing (default): INSERT INTO `roles` (`id`,`name`,`createdAt`,`updatedAt`) VALUES (?,?,?,?);
– Check MySQL database:


Sign Up

All Logs of Sign Up:
Processing func -> SignUp
Executing (default): INSERT INTO `users` (`id`,`name`,`username`,`email`,`password`,`createdAt`,`updatedAt`) VALUES (DEF
AULT,?,?,?,?,?,?);
Executing (default): SELECT `id`, `name`, `createdAt`, `updatedAt` FROM `roles` AS `roles` WHERE (`roles`.`name` = 'user
');
Executing (default): SELECT `createdAt`, `updatedAt`, `roleId`, `userId` FROM `user_roles` AS `user_roles` WHERE `user_r
oles`.`userId` = 1;
Executing (default): INSERT INTO `user_roles` (`createdAt`,`updatedAt`,`roleId`,`userId`) VALUES ('2020-06-08 08:07:03',
'2020-06-08 08:07:03',1,1);
– MySQL records:

SignIn and Access Protected Resources
– Adam can access api/test/user
url, can NOT access others.
Sign In:

Access Protected Resources:


– Jack can access api/test/user
& api/test/pm
url.
Can NOT access /api/test/admin
url.
Sign In:

Access Protected Resources:


– Thomas can access all URLs.
Sign In

Access Protected Resource:

Sourcecode
I includes sourcecode for the ‘Token Based Authentication in Node.js using JWT (JSON Web Tokens) + MySQL Example’ tutorial with below implemented features:
- Create Sequelize Models
- Sequelize Database Configuration
- Define RestAPIs Router
- Implement Express Controller
- Node.js Server
Nodejs JWT Authentication Example
Github sourcecode:
Github Sourcecode – Nodejs token based authentication
Very well written and done my friend!
You are the best! Keep Jwt authentication with nodejs tutorial