Node.js and Express.js part 7
working with user authentication using JWT
--
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…………