Node.js JWT Authentication & PostgreSQL – Express RestAPIs + JSON Web Token + BCryptjs + Sequelize

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-feature-image

JSON Web Token defines a compact and self-contained way for securely transmitting information as a JSON object.
In the tutorial, we show how to build a Nodejs Token Authentication RestAPIs with JSON Web Token (JWT) and PostgreSQL.

Related posts:
Sequelize Many-to-Many association – NodeJS/Express, MySQL
Node.js/Express RestAPIs CRUD – Sequelize ORM – PostgreSQL
– Fullstack with Angular: Angular & Nodejs JWT Authentication fullstack

Technologies

– Nodejs/Express
– Json Web Token
– BCryptjs
– Sequelize
– PosgreSQL

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 JWT
  • Information 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 ->

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-decoded-token

Overview

Project Structure

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-project-structure

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

    nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-many-to-many-user-role

  • 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

We will define 5 workflows as below ->

  • 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
    

    – 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
    

    – 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
    

    – Code in router.js ->

    app.get('/api/test/user', [authJwt.verifyToken], controller.userContent);
    
  • Access PM Content:
    	-> Verify JWT Token
    		-> If token is valid, verify PM role.
    			-> If User has Admin or PM role, controller will load & return Management Content to client.
    	-> Othercase, Error code will be returned
    

    – 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, verify ADMIN role.
    			-> If User has Admin role, controller will load & return Admin Content to client.
    	-> Othercase, Error code will be returned
    

    – Code in router.js ->

    app.get('/api/test/admin', [authJwt.verifyToken, authJwt.isAdmin], controller.adminBoard);
    
Goal

Sign Up ->

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-adam-sign-up

Sign In ->

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-adam-sign-in

Access API Successfully ->

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-adam-access-SUCCESSFUL-user-api

Unauthorized Access ->

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-adam-can-NOT-access-pm-api

Practice

Create Nodejs Project

Following the guide to create a NodeJS/Express project

Install Express, Sequelize, PostgreSQL, Json Web Token, Bcryptjs:

$npm install express sequelize pg pg-hstore jsonwebtoken bcryptjs --save

-> package.json file:

{
  "name": "nodejs-jwt-auth",
  "version": "1.0.0",
  "description": "Nodejs-JWT-Authentication-with-PostgreSQL-Sequelize-ORM",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "Nodejs",
    "Express",
    "JWT",
    "Sequelize",
    "PostgreSQL",
    "Authentication"
  ],
  "author": "grokonez.com",
  "license": "ISC",
  "dependencies": {
    "bcryptjs": "^2.4.3",
    "express": "^4.16.3",
    "jsonwebtoken": "^8.3.0",
    "pg": "^7.5.0",
    "pg-hstore": "^2.3.2",
    "sequelize": "^4.39.0"
  }
}

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 ->

const env = {
  database: 'testdb',
  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.

-> See more at: Sequelize Many-to-Many association – NodeJS/Express, MySQL

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 NOT
  • checkRolesExisted -> 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 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 NOT
  • isAdmin -> checking an User has ADMIN role or NOT
  • isPmOrAdmin -> 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) => {
	
	User.findById(req.userId)
		.then(user => {
			user.getRoles().then(roles => {
				for(let i=0; i {
	
	User.findById(req.userId)
		.then(user => {
			user.getRoles().then(roles => {
				for(let i=0; i
Implement Controller

- /app/controller/controller.js exports 5 funtions:

  • signup -> be used to register new User
  • signin -> be used to Login
  • userContent -> get User Info
  • managementBoard -> get Management Board Content
  • adminBoard -> 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.map(role => role.toUpperCase())
			}
		  }
		}).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']
};
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:

npm start

> nodejs-jwt-auth@1.0.0 start D:\gkz\article\Nodejs-JWT-Authentication\nodejs-jwt-auth
> node server.js

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), `username` 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 NULL, `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 NULL, `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 CASCADE) 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 (1,'USER','2018-09-30 20:11:40','2018-09-30 20:11:40');
Executing (default): INSERT INTO `roles` (`id`,`name`,`createdAt`,`updatedAt`) VALUES (2,'ADMIN','2018-09-30 20:11:40','2018-09-30 20:11:40');
Executing (default): INSERT INTO `roles` (`id`,`name`,`createdAt`,`updatedAt`) VALUES (3,'PM','2018-09-30 20:11:40','2018-09-30 20:11:40');

-> Check PostgreSQL database:

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-database-tables-schemas

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-database-tables-roles-tables

Sign Up

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-adam-sign-up

-> All Logs of Sign Up:

Processing func -> SignUp
Executing (default): INSERT INTO `users` (`id`,`name`,`username`,`email`,`password`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'Adam','adamgkz','adam@grokonez.com','$2a$08$qJts8G2RD7/J6RJGIPKxRuAKJTI1.C0WK93cvPQY0xutx6DWXv.PW','2018-09-30 20:14:08','2018-09-30 20:14:08');
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_roles`.`userId` = 1;
Executing (default): INSERT INTO `user_roles` (`createdAt`,`updatedAt`,`roleId`,`userId`) VALUES ('2018-09-30 20:14:08','2018-09-30 20:14:08',1,1);
Executing (default): SELECT `id`, `name`, `username`, `email`, `password`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`username` = 'jackgkz' LIMIT 1;
Executing (default): SELECT `id`, `name`, `username`, `email`, `password`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`email` = 'jack@grokonez.com' LIMIT 1;
Processing func -> SignUp
Executing (default): INSERT INTO `users` (`id`,`name`,`username`,`email`,`password`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'Jack','jackgkz','jack@grokonez.com','$2a$08$vr8m87P4Lhz4AmewyZEo4uq7zFQWAfg5qPZZq9itzdPPcNjwIy7Gu','2018-09-30 20:15:41','2018-09-30 20:15:41');
Executing (default): SELECT `id`, `name`, `createdAt`, `updatedAt` FROM `roles` AS `roles` WHERE (`roles`.`name` = 'pm');
Executing (default): SELECT `createdAt`, `updatedAt`, `roleId`, `userId` FROM `user_roles` AS `user_roles` WHERE `user_roles`.`userId` = 2;
Executing (default): INSERT INTO `user_roles` (`createdAt`,`updatedAt`,`roleId`,`userId`) VALUES ('2018-09-30 20:15:41','2018-09-30 20:15:41',3,2);
Executing (default): SELECT `id`, `name`, `username`, `email`, `password`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`username` = 'thomasgkz' LIMIT 1;
Executing (default): SELECT `id`, `name`, `username`, `email`, `password`, `createdAt`, `updatedAt` FROM `users` AS `users` WHERE `users`.`email` = 'thomas@grokonez.com' LIMIT 1;
Processing func -> SignUp
Executing (default): INSERT INTO `users` (`id`,`name`,`username`,`email`,`password`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'Thomas','thomasgkz','thomas@grokonez.com','$2a$08$hMKkxpOfvSIrFlNtPZ4JkuBIlp27CCZyH/6qo7kRhoBetP113b29C','2018-09-30 20:16:11','2018-09-30 20:16:11');
Executing (default): SELECT `id`, `name`, `createdAt`, `updatedAt` FROM `roles` AS `roles` WHERE (`roles`.`name` = 'admin');
Executing (default): SELECT `createdAt`, `updatedAt`, `roleId`, `userId` FROM `user_roles` AS `user_roles` WHERE `user_roles`.`userId` = 3;
Executing (default): INSERT INTO `user_roles` (`createdAt`,`updatedAt`,`roleId`,`userId`) VALUES ('2018-09-30 20:16:11','2018-09-30 20:16:11',2,3);

-> PostgreSQL records:

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-database-records-after-sign-up

SignIn and Access Protected Resources

- Adam can access api/test/user url, can NOT access others.

-> Sign In:

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-adam-sign-in

-> Access Protected Resources:

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-adam-access-SUCCESSFUL-user-api

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-adam-can-NOT-access-pm-api

- Jack can access api/test/user & api/test/pm url.
Can NOT access /api/test/admin url.

-> Sign In:

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-JACK-sign-in

-> Access Protected Resources:

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-JACK-access-SUCCESSFUL-user-api

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-JACK-access-SUCCESSFUL-PM-api

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-JACK-can-NOT-access-pm-api

Thomas can access all URLs.

-> Sign In:

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-thomas-sign-in

-> Access Protected Resource:

nodejs-jwt-authentication-express-bcryptjs-jsonwebtoken-sequelize-postgresql-THOMAS-access-SUCCESSFUL-ADMIN-api

SourceCode

Nodejs-JWT-Authentication



By grokonez | October 4, 2018.

Last updated on February 6, 2020.



Related Posts


2 thoughts on “Node.js JWT Authentication & PostgreSQL – Express RestAPIs + JSON Web Token + BCryptjs + Sequelize”

Got Something To Say:

Your email address will not be published. Required fields are marked *

*