Node.js and Express.js part 7

working with user authentication using JWT

Prakash Jha
6 min readOct 4, 2020

--

npm i jsonwebtoken bcryptjs

here we will create user registration using JWT

create new file models/User.js and add

const mongoose = require('mongoose');const UserSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Please add a name']
},
email: {
type: String,
required: [true, 'Please add an email'],
unique: true,
match: [
/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/,
'Please add a valid email'
]
},
role: {
type: String,
enum: ['user', 'publisher'],
default: 'user'
},
password: {
type: String,
required: [true, 'Please add a password'],
minlength: 6,
select: false
},
resetPasswordToken: String,
resetPasswordExpire: Date,
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('User', UserSchema);

now let's create a controller, create controllers/auth.js and add

const ErrorResponse = require('../utils/errorResponse');
const User = require('../models/User');
const asyncHandler = require('../middleware/async')
exports.register = asyncHandler(async(req,res,next) => {
res.status(200).json({success:true});
});

and now we need to make route, create route/auth.js

const express = require('express');
const {register} = require('../controllers/auth');
const router = express.Router();router.post('/register',register);module.exports = router;

and add this in server.js on respective places

const auth = require('./routes/auth');
app.use('/api/v1/auth',auth);

now we can test this route, mak post request and dont forget

{{URL}}/api/v1/auth/register 

now let's register the user and hash the password

go to controllers/auth.js and replace the whole code with

const ErrorResponse = require('../utils/errorResponse');
const User = require('../models/User');
const asyncHandler = require('../middleware/async')
exports.register = asyncHandler(async(req,res,next) => {
const { name, email, password, role } = req.body;
// Create user
const user = await User.create({
name,
email,
password,
role
});
res.status(200).json({success:true});
//sendTokenResponse(user, 200, res);
});

and in models/User.js add this part

const bcrypt = require('bcryptjs');UserSchema.pre('save', async function(next) {  const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});

get JSON web token

as we are here going to implement JWT we need to create token to check the same in jwt.io

add these two parameters in config/config.env

JWT_SECRET = ugff7ds6f7dsfsd9fdsgfdfdfJWT_EXPIRE=30d

in models/User.js add this method

UserSchema.methods.getSignedJwtToken = function() {
return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRE
});
};

and update the register method in controllers/auth.js

exports.register = asyncHandler(async(req,res,next) => {
const { name, email, password, role } = req.body;
// Create user
const user = await User.create({
name,
email,
password,
role
});
const token = user.getSignedJwtToken();
res.status(200).json({success:true,token:token});
//sendTokenResponse(user, 200, res);
});

now you send post request to “{{URL}}/api/v1/auth/register” with this in body

{"name":"john doe","email":"j@gmail.com","password":"123456","role":"publisher"}

we will receive a token which you can check in jwt.io it will give same id as the registered user’s

USER LOGIN

ok so now we need the code to login our user

add this in controllers/auth.js

exports.login = asyncHandler(async (req, res, next) => {
const { email, password } = req.body;

// Validate emil & password
if (!email || !password) {
return next(new ErrorResponse('Please provide an email and password', 400));
}

// Check for user
const user = await User.findOne({ email }).select('+password');

if (!user) {
return next(new ErrorResponse('Invalid credentials', 401));
}

// Check if password matches
const isMatch = await user.matchPassword(password);

if (!isMatch) {
return next(new ErrorResponse('Invalid credentials', 401));
}

const token = user.getSignedJwtToken();
res.status(200).json({success:true,token:token});
});

add this method in models/user.js

UserSchema.methods.matchPassword = async function(enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};

add this in routes/auth.js

const express = require('express');
const {register,login} = require('../controllers/auth');
const router = express.Router();router.post('/register',register).post('/login',login);module.exports = router;

so now if you try to login the user will get logged in and token will be generated

{"email":"j@gmail.com","password":"123456"}

try to login with this format

SENDING JWT IN A COOKIE

npm i cookie-parser

here we will store the token in the cookie, obviously at client side

replace the whole code of controllers/auth.js with

const ErrorResponse = require('../utils/errorResponse');
const User = require('../models/User');
const asyncHandler = require('../middleware/async')
exports.register = asyncHandler(async(req,res,next) => {
const { name, email, password, role } = req.body;
// Create user
const user = await User.create({
name,
email,
password,
role
});
sendTokenResponse(user, 200, res);
});
exports.login = asyncHandler(async (req, res, next) => {
const { email, password } = req.body;

// Validate emil & password
if (!email || !password) {
return next(new ErrorResponse('Please provide an email and password', 400));
}

// Check for user
const user = await User.findOne({ email }).select('+password');

if (!user) {
return next(new ErrorResponse('Invalid credentials', 401));
}

// Check if password matches
const isMatch = await user.matchPassword(password);

if (!isMatch) {
return next(new ErrorResponse('Invalid credentials', 401));
}

sendTokenResponse(user, 200, res);
});

// Get token from model, create cookie and send response
const sendTokenResponse = (user, statusCode, res) => {
// Create token
const token = user.getSignedJwtToken();

const options = {
expires: new Date(
Date.now() + process.env.JWT_COOKIE_EXPIRE * 24 * 60 * 60 * 1000
),
httpOnly: true
};

if (process.env.NODE_ENV === 'production') {
options.secure = true;
}

res
.status(statusCode)
.cookie('token', token, options)
.json({
success: true,
token
});
};

and include the given code in server.js

const cookieParser = require('cookie-parser');

and this right beneath the body parser

//cookie parser
app.use(cookieParser());

AUTH PROTECT MIDDLEWARE

we need to design a system such that only users can create bootcamps or update and delete and same applies for courses

create middleware/auth.js and add this code

const jwt = require('jsonwebtoken');
const asyncHandler = require('./async');
const ErrorResponse = require('../utils/errorResponse');
const User = require('../models/User');
// Protect routes
exports.protect = asyncHandler(async (req, res, next) => {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')
) {
// Set token from Bearer token in header
token = req.headers.authorization.split(' ')[1];
// Set token from cookie
}
// else if (req.cookies.token) {
// token = req.cookies.token;
// }
// Make sure token exists
if (!token) {
return next(new ErrorResponse('Not authorized to access this route', 401));
}
try {
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
console.log(decoded);
req.user = await User.findById(decoded.id);
next();
} catch (err) {
return next(new ErrorResponse('Not authorized to access this route', 401));
}
});

now we also need a method to get the logged in user to do this add a getme function in controllers/auth.js and add

exports.getMe = asyncHandler(async (req, res, next) => {
const user = await User.findById(req.user.id);

res.status(200).json({
success: true,
data: user
});
});

now we need to alter the routes to add the middleware to check if user is logged in or not

update the whole code of routes/auth.js with

const express = require('express');
const {register,login,getMe} = require('../controllers/auth');
const router = express.Router();const { protect } = require('../middleware/auth');router.post('/register',register).post('/login',login).get('/me',protect,getMe);module.exports = router;

update the whole code of routes/bootcamps.js with

const express = require('express');
const {
getBootcamps,
getBootcamp,
createBootcamp,
updateBootcamp,
deleteBootcamp,
getBootcampsInRadius,
bootcampPhotoUpload,
} = require('../controllers/bootcamps');
const Bootcamp = require('../models/Bootcamp');
const advancedResults = require('../middleware/advancedResults');
//include other resourse router
const courseRouter = require('./courses');
const router = express.Router();const { protect } = require('../middleware/auth');router.use('/:bootcampId/courses', courseRouter);router.route('/radius/:zipcode/:distance').get(getBootcampsInRadius);router
.route('/')
.get(advancedResults(Bootcamp, 'courses'), getBootcamps)
.post(protect, createBootcamp);
router.route('/:id/photo').put(protect, bootcampPhotoUpload);router
.route('/:id')
.get(getBootcamp)
.put(protect, updateBootcamp)
.delete(protect, deleteBootcamp);
module.exports = router;

update the whole code of routes/courses.js with

const express = require('express');
const {
getCourses,
getCourse,
addCourse,
updateCourse,
deleteCourse,
} = require('../controllers/courses');
const Course = require('../models/Course');
const advancedResults = require('../middleware/advancedResults');
const router = express.Router({ mergeParams: true });const { protect } = require('../middleware/auth');router
.route('/')
.get(
advancedResults(Course, {
path: 'bootcamp',
select: 'name description',
}),
getCourses
)
.post(protect, addCourse);
router
.route('/:id')
.get(getCourse)
.put(protect, updateCourse)
.delete(protect, deleteCourse);
module.exports = router;

okay so now to get the logged in user, you first need to login or register and get the token then in {{URL}}/api/v1/auth/me in headers add

key:Authorization
Value: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmN2ExMWUxODU3YTU1OGMwMjJlYTNmYiIsImlhd

the Bearer and token should be space-separated

now make get request to {{URL}}/api/v1/auth/me and you will get the logged in user, now also whiler creating, updating and deleting bootcamps and courses you need to

ROLE AUTHORIZATION

now we need to update our code such that only publisher and admin can create or update courses and bootcamps

go to middleware/auth.js and add this

// Grant access to specific roles
exports.authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(
new ErrorResponse(
`User role ${req.user.role} is not authorized to access this route`,
403
)
);
}
next();
};
};

update the whole routes/bootcamps.js with

const express = require('express');
const {
getBootcamps,
getBootcamp,
createBootcamp,
updateBootcamp,
deleteBootcamp,
getBootcampsInRadius,
bootcampPhotoUpload,
} = require('../controllers/bootcamps');
const Bootcamp = require('../models/Bootcamp');
const advancedResults = require('../middleware/advancedResults');
//include other resourse router
const courseRouter = require('./courses');
const router = express.Router();const { protect,authorize } = require('../middleware/auth');router.use('/:bootcampId/courses', courseRouter);router.route('/radius/:zipcode/:distance').get(getBootcampsInRadius);router
.route('/')
.get(advancedResults(Bootcamp, 'courses'), getBootcamps)
.post(protect,authorize('publisher','admin'), createBootcamp);
router.route('/:id/photo').put(protect, authorize('publisher','admin'),bootcampPhotoUpload);router
.route('/:id')
.get(getBootcamp)
.put(protect, authorize('publisher','admin'),updateBootcamp)
.delete(protect, authorize('publisher','admin'),deleteBootcamp);
module.exports = router;

and the whole routes/courses.js with this

const express = require('express');
const {
getCourses,
getCourse,
addCourse,
updateCourse,
deleteCourse,
} = require('../controllers/courses');
const Course = require('../models/Course');
const advancedResults = require('../middleware/advancedResults');
const router = express.Router({ mergeParams: true });const { protect,authorize } = require('../middleware/auth');router
.route('/')
.get(
advancedResults(Course, {
path: 'bootcamp',
select: 'name description',
}),
getCourses
)
.post(protect,authorize('publisher','admin'), addCourse);
router
.route('/:id')
.get(getCourse)
.put(protect, authorize('publisher','admin'),updateCourse)
.delete(protect,authorize('publisher','admin'), deleteCourse);
module.exports = router;

that’s it for this now…………

--

--