user authentication and authorization using passport-jwt strategy

When I hear the word authentication and authorization, I think attestation, verification and permission. In my previous post “a guide to creating a simple app” we developed a basic app, in this post we will build on what we did.

I assume you have already developed an app, if you have not I encourage you to check my post a guide to creating a simple app step 1 to step 15.

Terminologies:

Passport: Passport is an authentication middleware for node. There are over 500 different passport strategies. you can learn more about passport in the following link here

jwt-JSON Web Token: This is a passport strategy authenticating with JSON web token and uses jsonwebtoken library. Note that there are many ways to implement JWTs into an application.

(install passport dependencies)

npm install passport

(Install passport-jwt dependencies)

npm install passport-jwt

(Install bcrypt dependencies)

npm install bcrypt --save

(Create a new file and name it passport.js in config folder)

passport.js file

const JwtStrategy = require(‘passport-jwt’).Strategy;
const ExtractJwt = require(‘passport-jwt’).ExtractJwt;
const models = require(‘../models’);
const users = models.User;
const jwtOptions = {};
jwtOptions.jwtFromRequest=ExtractJwt.fromAuthHeaderAsBearerToken();
jwtOptions.secretOrKey = ‘myVerySecret’;
module.exports = passport =>{
passport.use(new JwtStrategy(
jwtOptions,(jwt_payload,done) =>{
users.findOne({where:{id:jwt_payload.id}})
.then(user =>{
console.log(user);
console.log(jwt_payload);
if(user){
return done(null,user);
}
return done(null, false)
})
.catch(err =>{
console.log(err)
});
}
));
};

When a user visits a protected route, the jwt will be attached to the authorization header, passport-jwt will get the value (in the headers) and parse it by jwtOptions.jwtFromRequest=ExtractJwt.fromAuthHeaderAsBearerToken();and from here series of activities goes on before deciding if the user will have access to the route or not. The codes above are explanatory but below I highlighted meaning to some terms which others might find confusing.

jwtOptions is an object that contains options used to control the extraction of token from the request.

jwtFromRequest is a function that accepts request and returns the jwt as null or a string.

secretOrKey contains the secret code for verifying token’s signature.

jwt_payload is an object containing the decoded JWT payload.

users.js file (in controller folder)

const models = require(‘../models’);
const bcrypt = require(‘bcrypt’);
const jwt = require(“jsonwebtoken”);
const passport = require(‘passport’);
const user = require(‘../models/user’);
const jwtStrategy = require(‘passport-jwt’).Strategy;
async function getUsers(req, res){
const view = await models.User.findAll();
res.json(view);
};
async function getSingleUser(req, res){
const view = await models.User.findOne({where:{id:req.user.id}});
res.json(view);
};
async function login(req, res){
var data = req.body;
const email = data.email;
const password = data.password;
const user = await models.User.findOne({where: {email}});
if(!user){
return res.json(“Try again”)
}
const checkPassword = bcrypt.compareSync(password, user.password);
if(!checkPassword){
return res.json(“password incorrect”)
}
const payload = {
id:user.id,
}
const token = jwt.sign(payload, “myVerySecret”);
res.json({
“token”: token,
“msg”: “SUCCESSFUL”,
“user”: user,
“statusCode”: 200
})
};
async function register(req, res){
const saltRounds = 10;
const salt = bcrypt.genSaltSync(saltRounds);
const hash = bcrypt.hashSync(req.body.password, salt);
var data = req.body;
data.password = hash;
const content = await models.User.create({firstName: data.firstName, lastName: data.lastName, email: data.email, password: data.password});
return res.json("Successfull");
};
module.exports = {
register,
login,
getUsers,
getSingleUser,
}

I think the only thing which is not straightforward and need enlightenment are the additional codes to login and register.

bcrypt.compareSync(password, user.password) is to compare the registered user password in the database and the password the user uses to login.

Note that the registered password of a user is being encrypted by adding some characters (could be over 15 different characters), this characters added are referred to salt and the number of times these characters are being modified is referred to as salt round in this case we are modifying 10 times after the characters have been modified and added to the user password it is referred to as hash. (hash is always in string datatype).

users.js file (in migration folder)

‘use strict’;
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable(‘Users’, {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
firstName: {
type: Sequelize.STRING
},
lastName: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable(‘Users’);
}
};

users.js file (in routes folder)

var express = require(‘express’);
const passport = require(‘passport’);
var router = express.Router();
const usersCntrl = require(‘../controller/users’);
router.post(‘/register’, usersCntrl.register);
router.post(‘/login’, usersCntrl.login);
router.get(‘/’, usersCntrl.getUsers);
router.get(‘/user’, passport.authenticate(“jwt”, {session: false}), usersCntrl.getSingleUser);
module.exports = router;

We use passport.authenticate()method on our route to authenticate a user. also, the Express app will run this middleware and will extract the JWT from postman header (Authorization) and verify with the public key, based on the result after comparing the password it will allow or disallow the user to visit the route. Note that session is false because we don’t need session when using jwt, also jwt was written in the function because that is the strategy method we are using.

users.js file (in seeders folder)

‘use strict’;
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.bulkInsert(‘Users’, [{
firstName: ‘John’,
lastName: ‘Doe’,
email: ‘johndoe@gmail.com’,
password: ‘johnde’,
createdAt: new Date(),
updatedAt: new Date()
}], {});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.bulkDelete(‘Users’, null, {});
}
};

app.js file

var createError = require(‘http-errors’);
var express = require(‘express’);
var path = require(‘path’);
var cookieParser = require(‘cookie-parser’);
var logger = require(‘morgan’);
const passport = require(‘passport’);
var usersRouter = require(‘./routes/users’);
var app = express();
// view engine setupapp.set(‘views’, path.join(__dirname, ‘views’));
app.set(‘view engine’, ‘jade’);
app.use(logger(‘dev’));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, ‘public’)));
app.use(passport.initialize());
require(‘./config/passport’)(passport);
app.use(‘/users’, usersRouter);
// catch 404 and forward to error handlerapp.use(function(req, res, next) {
next(createError(404));
});
// error handlerapp.use(function(err, req, res, next) {// set locals, only providing error in development res.locals.message = err.message;
res.locals.error = req.app.get(‘env’) === ‘development’ ? err : {};
// render the error pageres.status(err.status || 500);
res.render(‘error’);
});
module.exports = app;

In the app.js we will initialize passport by using passport.initialize() and also connect our passport.js file in the config folder.

Now it’s time we try what we did on postman!

(my port number is 3001, yours may be different)

make a post request to localhost:3001/users/register and register a user:

make a post request to localhost:3001/users/login and login the registered user:

make a get request to localhost:3001/users/user and retrieve the data of the user you registered:

To the value we added Bearer recall jwtOptions.jwtFromRequest=ExtractJwt.fromAuthHeaderAsBearerToken();the token is being extracted and compared to the user id (note that the token contains the user id) if the user id in the token and that of the user who logged in are the same the user gets authorized to navigate the site.

Note that the token was copied from login result and pasted to value in headers as shown in the image below:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTQsImlhdCI6MTYwNjg1MzQ1Nn0.ZmNOGYChRaoaZ9tmnHM-WxdsTbeeaGH1S7fsNUTqKaM

Conclusion: user authentication and authorization can be summarized into: encryption (adding characters to the user password), decryption(reading of password in the password encrypted to grant access to a user) and token generation and use(the use of token to compare user id).