Akash Rajput Technology over a cup of coffee

Zero to Hero – Node.js (part -6) – Secure REST APIs via JWT

2 min read

In the previous blog, we worked on exposing CRUD operations via REST APIs. But, there was something missing, something very important.

YES!!!, you got it right. I’m talking about Security. We don’t have any mean of authentication and security.

Traditionally, developers use method like cookies and sessions with user authentication. But still, that is not too secure. There are some savior like JSON Web Token(JWT) . JWT based authentication is simple and robust. And we have jsonwebtoken library in node inbuilt for us.

Before diving in for more in our coding, let’s first understand our project structure via below info-graphic (assuming server.js is our entry point). You will find this similar to our previous post, in fact, this is extension of previous post, so you can re-use the same application for this blog.

Info-graphic for DB APIs with JWT Authentication

We will use the previous application that we have built. We will just add other required stuff. For that, execute below command:

npm install bcryptjs jsonwebtoken express-validator@5.3.1 --save

bcryptjs is used to hash the password into the secure string and jsonwebtoken will be used to generate the secure and expirable token. express-validator is an npm library use to validate the body of the POST request.

So, let’s rock.

Open local.js which is inside configs/config/ and paste the below code. Please replace secret-code with any code you want.

let localConfig = {
hostname: 'localhost',
port: 3000,
secret : '123secret321',
};
module.exports = localConfig;
view raw lcoal.js hosted with ❤ by GitHub

app.js is 99% same as before with one extra import of express-validator. Change code like below:

const express = require("express");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
const expressValidator = require('express-validator');
let routes = require("../routes/routes");
let server = express();
let create = (config, db) => {
server.set("env", config.env);
server.set("port", config.port);
server.set("hostname", config.hostname);
// add middleware to parse the json
server.use(bodyParser.json());
server.use(expressValidator());
server.use(
bodyParser.urlencoded({
extended: false
})
);
//connect the database
mongoose.connect(db.database, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true
});
routes.init(server);
};
let start = () => {
let hostname = server.get("hostname"),
port = server.get("port");
server.listen(port, function() {
console.log(
"Express server listening on - http://" + hostname + ":" + port
);
});
};
module.exports = {
create: create,
start: start
};
view raw app.js hosted with ❤ by GitHub

In model, we will have username, password and email for now. you can have more information if you want in your model. For sample:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let userSchema = new Schema(
{
name : {
type:String,
required:[true,'Name is required']
},
email : {
type:String,
required:[true,'Email is required'],
unique : true
},
password: {
type: String,
required : [ true, 'password is required']
}
},
{
timestamps:true
}
);
module.exports = mongoose.model("user", userSchema);
view raw user.js hosted with ❤ by GitHub

In this blog, we will have another segment as our middleware. In this, we will code our validation as well as authentication areas.

Create middlewares folder in root structure and create validation.js and authgaurd.js.

In validation, we will validate the required properties of the POST body like username, email, and password. Sample code like below:

const {body} = require('express-validator/check');
const validateRegistrationBody = () => {
return [
body('name')
.exists()
.withMessage('name field is required')
.isLength({min:3})
.withMessage('name must be greater than 3 letters'),
body('email').exists()
.withMessage('email field is required')
.isEmail()
.withMessage('Email is invalid'),
body('password')
.exists()
.withMessage('password field is required')
.isLength({min : 8,max: 12})
.withMessage('password must be in between 8 to 12 characters long')
]
}
const validateLoginBody = () => {
return [
body('email').exists()
.withMessage('email field is required')
.isEmail()
.withMessage('Email is invalid'),
body('password')
.exists()
.withMessage('password field is required')
.isLength({min : 8,max: 12})
.withMessage('password must be in between 8 to 12 characters long')
]
}
module.exports = {
validateRegistrationBody : validateRegistrationBody,
validateLoginBody : validateLoginBody
}
view raw validation.js hosted with ❤ by GitHub

In this file, we are importing check from express-validator which provides various powerful functions. Rest fo the code is self-explanatory.

In authgaurd.js file validates the token for all the restricted API before performing any action on the requested data.

In this file, we are using the jsonwebtoken library with our already defined secure string in the local.js file to secure node.js REST API.

First, we get the token from the headers of the request and then validate it with our secure string. If the token is valid then it processes the API otherwise return a response with Invalid Token message to the client.

Token-based authentication is very helpful to secure node.js REST API. Due to its easiness and security, these days developers use mostly token to secure their API’s from the un-authenticated users.

Sample code as below:

const express = require('express');
const jwt = require('jsonwebtoken');
const config = require('../configs/config/config');
const authClientToken = async(req,res,next)=>{
let token = req.headers['x-access-token'];
if(!token)
{
return res.status(401).json({
'errors' : [{
'message' : " No Authentication token provided in Header"
}]
});
}
jwt.verify(token,config.secret,(error,decode) => {
if(error)
{
return res.status(401).json({
'errors' : [{
'message' : "Invalid Token"
}]
});
}
return next();
});
}
module.exports = {
authClientToken : authClientToken
}
view raw authgaurd.js hosted with ❤ by GitHub

In our Services module, we will create another folder named auth. Inside it, we will create auth.js file, which will export 2 main functions names register and login.

register function will be used to register a user by validating minimum requirements like email and password. After that, these values will be saved in our MongoDb.Also, validation of requested body will be done via help of validation middleware. If the body is valid then we check that if user email already exists or not, if the user email already exists then we send a response back to the client with the HTTP status code 409 and a message stating that email already exists otherwise create the user and send a successful response to the user.

login function will be used to log in the user and give him/her permission to use our API by generating the token.

Sample code like below:

const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const {validationResult} = require('express-validator/check');
const config = require('../../configs/config/config');
const userModel = require('../../models/user');
const register = async(req,res,next) => {
let errors = validationResult(req);
if(!errors.isEmpty())
{
return res.status(422).json({
'errors':errors.array()
});
}
let {name, email, password} = req.body;
let isEmailExists = await userModel.findOne({"email" : email});
if(isEmailExists)
{
return res.status(420).json({
"errors":[
{
"message" : "Email already exists in system"
}
]
});
}
let hashedPassword = await bcrypt.hash(password,8);
try {
let user = await userModel.create({
name:name,
email: email,
password: hashedPassword
});
if(!user)
{
throw new error();
}
return res.status(201).json({
"success":[
{
"message" : "User Registration successful"
}
]
});
} catch (error) {
console.log(error);
return res.status(500).json({
'errors':[{
"message": "Error occured while creating user in system"
}]
});
}
}
const login = async(req,res,next) => {
const errors = validationResult(req);
if(!errors.isEmpty())
{
return res.status(422).json({
"errors":errors.array()
});
}
let {email,password} = req.body;
try{
let isUserExists = await userModel.findOne({"email":email});
let isPasswordValid = await bcrypt.compare(password,isUserExists.password);
if(!isUserExists || !isPasswordValid)
{
return res.status(401).json({
"error":[{
"message" : "Email/Password is wrong"
}]
});
}
let token = jwt.sign({id:isUserExists._id}, config.secret, {expiresIn:86400});
return res.status(200).json({
"success":[{
"message" : "User logged in successfully",
"email" : email,
"token" : token
}]
});
}
catch(error)
{
console.log(error);
return res.status(500).json({
"errors":[{
"message" : "There was a problem while login user"
}]
});
}
}
module.exports ={
register : register,
login : login
}
view raw auth.js hosted with ❤ by GitHub

I’ve created full project and posted it on github, fork it If you want. Changes in other files are very simple and I want you guys to figure it out where are the changes has been made. If you still need, please comment out and I’ll share the video where I’ve explained all.

Till next…….

Akash Rajput Technology over a cup of coffee

One Reply to “Zero to Hero – Node.js (part -6) – Secure…”

Leave a Reply

Your email address will not be published. Required fields are marked *

Never miss a story from me, get updates directly in your inbox.
Loading