Hey Dev's
If you read my previous articles about JWT (jsonwebtoken) you will come to understand one thing. Implementing authentication and authorization is a daunting process. You will need to be very detailed with each process because the security of your application should be the number one priority.
To handle our authentication let us use passport.js in today's tutorial. Read more about Passport.js http://www.passportjs.org/. You will be able to add Facebook, Twitter, etc in your application with little effort. They have done all the work required.
Passport is authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped into any Express-based web application. A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more.
We have this article https://dev.to/kevin_odongo35/jwt-authorization-and-authentication-node-express-and-vue-2p8c. We had used JWT for authentication. Let us replace it with Passport.js in case you want to.
The benefits of using Passport.js will be immense. You will be able to implement different strategies in your application. Passport.js currently has over 502 strategies to choose from.
NOTE
Passport,js will handle authentication but not authorization level. There are scenarios where they can give authorization as well
yarn add passport serve-static cookie-parser express-session passport-local
Let us update the index.js file as follows.
index.js
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
var passport = require('passport')
var LocalStrategy = require('passport-local').Strategy;
var bcrypt = require("bcryptjs");
require('dotenv').config()
const app = express();
// parse application/json
app.use(bodyParser.json())
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }))
var corsOptions = {
credentials: true,
origin: ['http://localhost:3000', 'http://localhost:8080', 'http://localhost:8080']
}
// use cors options
app.use(cors(corsOptions))
app.use(require('serve-static')(__dirname + '/../../public'));
app.use(require('cookie-parser')());
app.use(require('express-session')({ secret: 'keyboard cat', resave: true, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
// database
const db = require("./app/models");
const User = db.user;
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }).populate("roles", "-__v")
.exec((err, user) => {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
var passwordIsValid = bcrypt.compareSync(
password,
user.password
);
if (!passwordIsValid) {
return done(null, false, { message: 'Incorrect password.' });
}
var authorities = [];
for (let i = 0; i < user.roles.length; i++) {
authorities.push("ROLE_" + user.roles[i].name.toUpperCase());
}
// user details
const user_information = {
id: user._id,
username: user.username,
email: user.email,
roles: authorities,
}
return done(null, user_information);
});
}
));
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
db.mongoose
.connect(db.url, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
})
.then(() => {
console.log("Connected to the database!");
initialize();
})
.catch(err => {
console.log("Cannot connect to the database!", err);
process.exit();
});
const Role = db.role
function initialize() {
Role.estimatedDocumentCount((err, count) => {
if (!err && count === 0) {
new Role({
name: "user"
}).save(err => {
if (err) {
console.log("error", err);
}
console.log("added 'user' to roles collection");
});
new Role({
name: "admin"
}).save(err => {
if (err) {
console.log("error", err);
}
console.log("added 'admin' to roles collection");
});
}
});
}
// routes
const blog = require('./app/routes/blog') // blog routes
const auth = require('./app/routes/auth') // user authentication
app.use('/api/blog',blog, function(req, res, next){
res.header(
"Access-Control-Allow-Headers",
"Origin, Content-Type, Accept"
);
next();
}) // user authorization
app.use('/api/auth', auth, function(req, res, next){
res.header(
"Access-Control-Allow-Headers",
"Origin, Content-Type, Accept"
);
next();
}) // auth authentication
// listening port
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}.`);
});
In our routes, we now need to update the auth.js. The signing for the application will be handled by passport.js
/routes/auth.js
const { verify_user_email } = require("../authentication");
const express = require("express")
const router = express.Router()
const auth = require("../controller/auth.controller");
var passport = require('passport')
router.post("/signin", passport.authenticate('local'),
function(req, res) {
res.status(200).send({
id: req.user.id,
username: req.user.username,
email: req.user.email,
roles: req.user.roles,
});
});
// router.post("/signin", auth.signin)
router.post("/signup",
[
verify_user_email.checkDuplicateUsernameOrEmail,
verify_user_email.checkRolesExisted
],
auth.signup
)
module.exports = router
/routes/blog.js
We need to remove the auth_jwt_token.verifyToken which we were using with JWT. This because with passport.js localStrategy (You can choose a different strategy) will verify the authentication and provides a unique cookie for the session. You will NOTE for DELETE CRUD we still have to check for the role.
In the file called auth.js in the authentication folder. Let us add a new function that will be verifying each request. This is a simple one we will be checking the availability of cookies for each request from our front end.
...
verify_passport = (req, res, next) => {
if(!req.cookies[`connect.sid`]){
res.status(403).send({ message: "Unauthorized!" });
return;
}
next();
}
const authJwt = {
verify_passport,
verifyToken,
isAdmin,
};
module.exports = authJwt;
Once we have added the function let us change the blog.js file to reflect which function to use in verifying authentication.
const express = require("express")
const router = express.Router()
const blog = require("../controller/blog.controller");
const { auth_jwt_token } = require("../authentication");
// /api/blog: GET, POST, DELETE
// /api/blog/:id: GET, PUT, DELETE
// /api/blog/published: GET
// Create a new blog
router.post("/", [auth_jwt_token.verify_passport], blog.create)
// Retrieve all blog
router.get("/", blog.findAll);
// Retrieve all published blog
router.get("/published", blog.findAllPublished);
// Retrieve a single blog with id
router.get("/:id", [auth_jwt_token.verify_passport], blog.findOne);
// Update a blog with id
router.put("/:id", [auth_jwt_token.verify_passport], blog.update);
// Delete a blog with id
router.delete("/:id", [auth_jwt_token.verify_passport, auth_jwt_token.isAdmin], blog.delete);
// Create a new blog
router.delete("/", [auth_jwt_token.verify_passport, auth_jwt_token.isAdmin], blog.deleteAll);
module.exports = router
With that, you will have one source of function that checks authentication that you can expound further to ensure it meets your application security measures.
In your front end for every request ensure you send each request withCredentials = true. Do a security read https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials about using withCredntials in your request to ensure your application will be robust and secure.
Here is an example of how the mongo.express.js will be in the front end.
import axios from "axios";
// create new blog
export const createnewblog = async item => {
let data = {
author: JSON.stringify({
name: item.author.name,
email: item.author.email,
about: item.author.about
}), // replace with auth user
content: JSON.stringify({
title: item.content.title,
src: item.content.src,
text: item.content.text
})
};
let request = {
url: "http://localhost:3000/api/blog", // should be replaced after going to production with domain URL
withCredentials: true,
method: "post",
headers: {
"Content-type": "application/json"
},
data: JSON.stringify(data)
};
const response = await axios(request);
return response;
};
// delete blog
export const deleteblog = async item => {
let request = {
url: "http://localhost:3000/api/blog/" + item, // should be replaced after going to production with domain URL
withCredentials: true,
method: "delete",
headers: {
"Content-type": "application/json"
}
};
const response = await axios(request);
return response;
};
// update blog
export const updateblog = async item => {
let data = {
author: JSON.stringify({
name: item.author.name,
email: item.author.email,
about: item.author.about
}), // replace with auth user
content: JSON.stringify({
title: item.content.title,
src: item.content.src,
text: item.content.text
}),
published: item.published
};
let request = {
url: "http://localhost:3000/api/blog/" + item._id, // should be replaced after going to production with domain URL,
withCredentials: true,
method: "put",
headers: {
"Content-type": "application/json"
},
data: JSON.stringify(data)
};
const response = await axios(request);
return response;
};
// get all blog
export const retriveallblog = async () => {
let request = {
url: "http://localhost:3000/api/blog", // should be replaced after going to production with domain url
method: "get",
headers: {
"Content-type": "application/json"
}
};
const response = await axios(request);
return response;
};
CONCLUSION
For applications using node and express for authentication try to learn passport.js it will give your application more room to add more authentication strategies with ease OR use AWS Cognito which also supports Facebook, Google, and Twitter login. I will do a tutorial on how to use AWS Cognito with Node, Express, and Mongo DB.
So far if you followed these tutorials then your application will be 2/3rds done. Just some test writing and polish then deploy to production.
I hope this tutorial has helped someone. Keep close to our next tutorial.
Thank you and have a good week ahead.
Top comments (0)