Authentication with Nodejs [bcrypt, express-session, jwt]

In this article, we will look through the authentication process. We will work on Nodejs and we will be using express, bcrypt, express-session jwt and other necessary packages. Here we will first look through the basic authentication process to understand the authentication process.

Once after that, we will learn what should and should not do on the real application, how to hash the password before saving it to the database and validate on login. Then we will look through authentication using JWT, for API (bearer authentication).



Prerequisite

If you are looking at this post, I assume that you already have some basic understanding of Nodejs. If you are here just to understand the flow of authentication, you can follow along as well.



Authentication

Authentication is the process of determining whether someone or something is, in fact, who or what it says it is. Authentication technology provides access control for systems by checking to see if a user's credentials match the credentials in a database of authorized users or in a data authentication server.

Authentication Vs Authorization, I think talking about Authorization may also help us to understand the authentication.

Authentication is the process of verifying that "you are who you say you are", Authorization is the process of verifying that "you are permitted to do what you are trying to do".

Info! Only necessary code snippet are show. Find full project on Github.



Basic Authentication

We are using express, express-session, ejs is used as templating engine.



import express from 'express';
import session from 'express-session'

const app = express();

app.set("view engine", "ejs");

app.use(express.urlencoded({
extended: true
}));

app.use(session({
secret: "aSecret-UseENV",
resave: false,
saveUninitialized: false
}))


Let's implement basic authentication. Here we will signup and login.



app.post("/signup", async (req, res, next) => {
let name = req.body.name;
let email = req.body.email;
let password = req.body.password;

let newUser = new User({
name: name,
email: email,
password: password
})
try {
let user = await newUser.save();

res.render('signup-result', {
user: user,
message: "Account Created",
message2: false,
success: true
});

} catch (error) {

res.render('signup-result', {
message: "Account Creation Failed",
message2: 'The Email may have been already used.',
success: false
});
}


})



Here, we simply get the name, email, and password from the form and save it to the database.

When the user login, we can now get the provided email and password, compare it to the database, and if the data is matched, we can authenticate the user and set the season.



app.post("/login", (req, res, next) => {

let email = req.body.email;
let password = req.body.password;

User.findOne({ email: email, password: password }).then(user => {
if (user) {

//let's save the user in the session
req.session.user = user;

res.render('login-result', {
message: "Login Success",
message2: false,
success: true
});
} else {
res.render('login-result', {
message: "Login Failed",
message2: "Email and password did not match.",
success: false
});

}
})
})


As you can see on the above code snippet, we are finding the user with that email and that password. If Found, the user is found else, an error message is thrown.

Now, let's focus on  req.session.user = user;  this line. As we are using the express-session as middleware, the req object has the session object. Here we are assigning the user property on the session object to the user (the user found by matching the password and the email).


In doing so, (on modifying the session object) express-session will do two things.

  1. Create a session Id for this request and attach that session id as a cookie to the response object.
  2. Save the session object, in such a way that it can get this exact data using the session id. for simplicity it is saved as key-value pair, session Id being the key and the session object being the value.

As we know the cookies, it will be sent to the subsequent requests, In the express-session middleware, it checks for the session Id, if it matches with anything that is on the express-session data store. It will populate the session object with the saved value.


Thus, we can check the object on the session object, If it is defined, the user is authenticated, else he/she is not.



const isAuth = (req, res, next) => {
if (req.session.user) {
//if user exists on session
next();
} else {
// else redirect to login
res.redirect('/login');
}
}

// a protected route
app.get("/me", isAuth, (req, res, next) => {
let user = req.session.user;
res.render('me', {
user: user
});
})



That's it. If you understand that's good. else I would suggest you to read the Basic Authentication section again.




Hashing the Password with bcrypt

In the above section, we saved the plain text of the password in the database. In the real world application, we will never save the user passwords on plain text. We should hash the password before saving it to the database.



import { hash, compare } from 'bcrypt';

app.post("/signup", async (req, res, next) => {
let name = req.body.name;
let email = req.body.email;
let password = req.body.password;

// let's make use of bcrypt to hash
let hashedPassword = await hash(password, 10);

let newUser = new User({
name: name,
email: email,
password: hashedPassword
})

try {
let user = await newUser.save();

res.render('signup-result', {
user: user,
message: "Account Created",
message2: false,
success: true
});

} catch (error) {

res.render('signup-result', {
message: "Account Creation Failed",
message2: 'The Email may have been already used.',
success: false
});
}


})

app.post("/login", (req, res, next) => {

let email = req.body.email;
let password = req.body.password;

//now we will get the user, then compare the hashedPassword (saved in DB)
// with currently provided password
User.findOne({ email: email }).then(async user => {
if (user) {

let passwordMatched = await compare(password, user.password);

if (passwordMatched) {
// email and password matched

//let's save the user in the session
req.session.user = user;

res.render('login-result', {
message: "Login Success",
message2: false,
success: true
});
} else {

//password did not match
res.render('login-result', {
message: "Login Failed",
message2: "Email and password did not match.",
success: false
});
}



} else {

//user not found associated with that mail
res.render('login-result', {
message: "Login Failed",
message2: "Email and password did not match.",
success: false
});

}
})
})


Now, on signup, we will hash the password before saving it to the database. On log in, we will get the user based on the email only. Then we can use the compare method from bcrypt to validate if the entered password and hashed password are the same or not. If the method returns true, the password matched, else the password did not match with the email.



Bearer Authentication / JWT Authentication

Or we can say API authentication. 

The signup will be the same but will respond as API this time. In Login, we get the user information, sign it using a secret. and respond back. We are encoding the data in such a way that the data can be decoded using only the same secret.

More preciously if the token will be invalid if the user attempts to modify it in any way. The token will be invalid when validating if the wrong secret (other than the server's secret) is used to sign it.



const JWT_SECRET = 'secretFromEnv';

app.post("/signup", async (req, res, next) => {
let name = req.body.name;
let email = req.body.email;
let password = req.body.password;

// let's make use of bcrypt to hash
let hashedPassword = await hash(password, 10);

let newUser = new User({
name: name,
email: email,
password: hashedPassword
})

try {
let user = await newUser.save();
user = user.toObject();
let { password, ...userWithoutPass } = user;

console.log(userWithoutPass);

res.status(201).json({
message: "Account created",
user: userWithoutPass
})

} catch (error) {
console.log(error.toString());

//
res.status(400).json({
message: "error on creating account."
})
}


})

app.post("/login", (req, res, next) => {

let email = req.body.email;
let password = req.body.password;

//now we will get the user, then compare the hashedPassword (saved in DB)
// with currently provided password
User.findOne({ email: email }).lean().then(async user => {
if (user) {

let passwordMatched = await compare(password, user.password);

if (passwordMatched) {
// email and password matched

let { password, ...userWithoutPass } = user;


let token = jwt.sign({ user: userWithoutPass }, JWT_SECRET, {
expiresIn: '7d'
// expressed in seconds or a string describing a time span https://github.com/zeit/ms.js.
})


res.status(200).json({
message: "Login Success",
token: token,
user: userWithoutPass
});
} else {

//password did not match
res.status(403).json({
message: "Email and password did not match.",
});
}

} else {

//user not found associated with that mail
res.status(403).json({
message: "Email and password did not match.",
});

}
})
})

const isAuth = (req, res, next) => {
// console.log(req.headers)
if (!req.headers.authorization) {
return res.status(401).json({
message: "Authorization Header missing"
})
}
let authorization = req.headers.authorization;
let token = authorization.split(" ")[1];
// console.log(token);

let jwtData;
try {
jwtData = jwt.verify(token, JWT_SECRET);

} catch (error) {
console.log(error);

return res.status(401).json({
message: "Invalid Token."
})
}

req.user = jwtData.user;
next();

}

app.get("/me", isAuth, (req, res, next) => {
let user = req.user;
User.findOne({ _id: user._id }).lean().then(user => {

delete user.password;

res.status(200).json({
message: "User Profile",
user
})
})
})


In the isAuth middleware, we check if the authentication header is presented. If it is, we will get the bearer token, remove the bearer part, verify the token. and allow to next chain if validated.


Github Repository: https://github.com/InfoDevkota/Authentication-With-Nodejs





As we can see in the above image, we can send API requests to protected routes using the Bearer token as an authentication mechanism.



Conclusion

In This article, we look through various methods of authentication. Here we look at template-based authentication, using express-session bcrypt and ejs.

We Implement Bearer Authentication using JWT. Here we try to understand the authentication workflow as well.


Posted By: Sagar Devkota

0 Comments