Authentication with JWT token using Express JS

Authentication with JWT token using Express JS

Content covered-

  1. What is JWT

  2. Project setup

  3. User creation

  4. User Login

  5. Verifying the Refresh token and generating a new access token using the Refresh token

  6. Verifying the access token for all authorized endpoints

  1. What is JWT

JWT a.k.a(JSON WEB TOKEN) is token-based authentication through which we can verify the user identity and allow access to the application if a user is found to be registered with valid credentials

JWT consists of Header, Payload, and Signature separated by dots (.) Eg: xxxx.yyyy.zzzz

Header -

The header consists of a token type and algorithm used which in our case is JWT and HS256 respectively

Eg-

{

"alg": "HS256",

"typ": "JWT"

}

Payload -

It consists of user information which is sent while creating token, token creation and expiry time

Eg-

{

"user_id": "6314c5c21e066cf54427dcf2",

"email": "goofranshaikh@gmail.com",

"iat": 1665211626,

"exp": 1665211746

}

Signature -

To sign the JWT token it uses header , payload , secret key and algorithm specified in header

Eg-

HMACSHA256(

base64UrlEncode(header) + "." +

base64UrlEncode(payload),

secret)

  1. Project setup

Open the terminal in the project folder and run the below command to install all the dependencies

  • npm install express bcryptjs dotenv jsonwebtoken mongoose

  • Create .env file on your root folder and define some environment variable which we will use in the project

.env

API_PORT=3000

MONGO_URI= mongodb://localhost:27017

TOKEN_KEY="accessToken@123"

REFRESH_TOKEN="refreshToken@123"

  • Create a config folder and create database.js file, In our case, we are using mongodb as a database

database.js

const mongoose = require("mongoose");

const { MONGO_URI } = process.env;

exports.connect = () => {

// Connecting to the database

mongoose

.connect(MONGO_URI, {

useNewUrlParser: true,

useUnifiedTopology: true,

})

.then(() => {

console.log("Successfully connected to database");

})

.catch((error) => {

console.log("database connection failed. exiting now...");

console.error(error);

process.exit(1);

});

};

  • Create index.js file in your root folder, here we're going to create our server and listen to it

index.js

const http = require("http");

const app = require("./app");

const server = http.createServer(app);

const { API_PORT } = process.env;

const port = process.env.PORT || API_PORT;

// server listening

server.listen(port, () => {

console.log(`Server running on port ${port}`);

});

  1. User Creation -

First, we are going to create the file app.js in our root folder , where we going to implement the logic for registering the user and its login process

  1. Creating the user schema object , which defines the user document object in mongoDB

Create model folder in base folder and add use.js file in it

(Model → user.js)

user.js

const mongoose = require("mongoose"); //importing mongoose

const userSchema = new mongoose.Schema({ //defining schema object to be stored in mongoDB collection

firstName: { type: String, default: null },

lastName: { type: String, default: null },

email: { type: String, unique: true },

password: { type: String }

});

module.exports = mongoose.model("blogUser", userSchema); //Create model with 'blogUser' as collection name inside mongoDB

app.js

require("dotenv").config();

require("./config/database").connect();

const express = require("express");

const bcrypt = require('bcryptjs'); //to encrypt the password of the user while saving to db

const jwt = require('jsonwebtoken')

// importing user model

const User = require("./model/user");

app.use(express.json());

// Register

app.post("/register", async (req, res) => {

// Our register logic starts here

try {

// Get user input

const { firstName, lastName, email, password } = req.body;

// Validate user input

if (!(email && password && firstName && lastName)) {

res.status(400).send("All input is required");

}

// check if user already exist

// Validate if user exist in our database

const oldUser = await User.findOne({ email });

if (oldUser) {

return res.status(409).send("User Already Exist. Please Login");

}

//Encrypt user password

encryptedPassword = await bcrypt.hash(password, 10);

// Create user in our database

const user = await User.create({

firstName,

lastName,

email: email.toLowerCase(),

password: encryptedPassword,

});

res.status(201).json(user);

} catch (err) {

console.log(err);

}

// Our register logic ends here

});

In the above code when we hit the /register endpoint, first it checks
for all the required inputs,
If not found then we can send a bad request status code with the error message,

But if all the required inputs are provided by the user then we are checking if the user already exists in the database using User.findOne() method

If the user not exist then we are hashing the password using bcrypt library before saving the user to the DB,

User.create() method will insert the user record under the “blogusers” collection name

Let’s try to hit the endpoint on postman

Record inserted in the mongoDB

4. User Login

Write the below code in app.js

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

// Our login logic starts here

try {

// Get user input

const { email, password } = req.body;

// Validate user input

if (!(email && password)) {

res.status(400).send("All input is required");

}

// Validate if user exist in our database

const user = await User.findOne({ email });

if (user && (await bcrypt.compare(password, user.password))) {

// Create token

const accesstoken = jwt.sign(

{ user_id: user._id, email },

process.env.TOKEN_KEY,

{

expiresIn: "5m",

}

);

const refreshtoken = jwt.sign(

{ user_id: user._id, email },

process.env.REFRESH_TOKEN,

{

expiresIn: "2h",

}

)

// save user token

const accessToken= accesstoken;

const refreshToken =refreshtoken

// user

res.status(200).json({user,accessToken,refreshToken});

}

res.status(400).send("Invalid Credentials");

} catch (err) {

console.log(err);

}

// Our register logic ends here

});

When the user hits /login route we are checking for the required input if found then we are proceeding forward and checking for the credentials provided by the user are valid or not, By using bcrypt.compare method we are checking payload request password with the password saved in DB for the corresponding user If this is matched then we are generating access token with the expiry of 5 minutes and refresh token with the expiry of 2 hours

In the above example, we have an access token to access the authorized endpoints, without this access token in the request we will not allow the user to access the data and return 401 unauthorized error to the user

Once this token expires in 5 minutes then user will not be able to access the data and gets logged out, if we are going to increase the token expiry time then that might be a security issue, so to solve this issue we have a refresh token, once the access token gets expired then the user will send refresh token, without the user getting logged out we will receive new access token and also security will not get compromised

5.Verifying Refresh token and generating new access token using the Refresh token

app.post("/refresh",async(req,res)=>{

try{

const {email,refreshToken}=req.body

if(!refreshToken){

return res.status(400).send("refresh Token is required")

}

const isVerified=verifyRefreshToken(email,refreshToken)

if(isVerified){

const accToken= jwt.sign({email},

process.env.TOKEN_KEY,

{expiresIn:"5m"}

)

res.status(200).send({token:accToken})

}

else{

console.log('refresh token not valid')

res.status(401).send('Unauthorized')

}

}

catch(err)

{

console.log(err)

}

})

We are getting user's email and refresh token from the user to generate the access token again, first, we are checking if the user has sent the refresh token in the request body if found then we are verifying the validity of the refresh token in verfyRefreshToken method (we will see this method later in blog), if this method returns TRUE then this is correct refresh token so we will generate the new access token and send it back to the user

Verifying refresh token validity

(middleware/verifyRefresh.js)

In jwt.verify method we are passing a token and secret key, which will decode the refresh token and give us the user info, since we know while we have created the token we have passed user's email, so while decoding we are comparing it with the logged in user email,if both the emails are matched then this is authenticated user and we are returning true in that case which indicates refresh token is valid

const jwt = require("jsonwebtoken");

const config = process.env;

function verifyRefreshToken(email,token){

try{

const decodedToken = jwt.verify(token,config.REFRESH_TOKEN)

return decodedToken.email== email? true:false

}

catch(err){

res.send(err)

}

}

module.exports=verifyRefreshToken

Verifying access token validity

(middleware/auth.js)

This is the middleware for protecting the API endpoints,
In this, we are accepting authorization tokens from the user in headers, request body, and query params,if it is not found then we are returning 403 error code which indicates the user is forbidden to access the endpoint.
In case of the token is present we are verifying the token and allowing user to the endpoint if the token is found to be true, else we are returning 401 error code, which indicated the user is unauthorized to access the endpoint

auth.js

const jwt = require("jsonwebtoken");

const config = process.env;

const verifyToken=(req, res, next)=> {

console.log(req.headers["authorization"].split(' ')[1])

const token =

req.body.token || req.query.token || req.headers["authorization"].split(' ')[1];

if (!token) {

return res.status(403).send("A token is required for authentication");

}

try {

const decoded = jwt.verify(token, config.TOKEN_KEY);

console.log(decoded,'decode')

req.user = decoded;

} catch (err) {

return res.status(401).send(err);

}

return next();

};

module.exports = verifyToken;

6. Verifying the access token for all authorized endpoints

Authenticated route

app.post("/welcome" ,verifyToken,(req, res) => {

res.status(200).send(JSON.stringify("Hey, have you found this blog useful..."));

});

module.exports = app;

Hope you have find the blog useful

Below is the github repo of the code
https://github.com/GoofranShaikh/blogpostweb