JWT Authentication  in NodeJS

JWT Authentication in NodeJS

February 28, 2024 (8mo ago)

javascriptnodejsjwtbeginnersnodejs-developerchaicode

Introduction

Authentication is a crucial aspect of modern web applications. JSON Web Tokens (JWT) have become a popular method for implementing authentication in web applications because of it's simplicity, security, and scalability.

In this Article We'll discuss about What is JWT, How it works and How to use it using a sample project!

So without delaying further, Let's Get Started!

What is JWT?

JWT Image

Before we begin let's understand what is a JWT.

So JWT, which stands for JSON Web Token, is an open standard for securely sharing JSON data between parties. The data is encoded and digitally signed, which ensures its authenticity.

JWT is widely used in API authentication and authorization workflows, as well as for data transfer between clients and servers.

JWT Structure:

The components of a JWT, visualized.

A JWT has three sections: a header, a payload, and a signature. Each sections are separated by periods. A typical JWT looks like this:

xxxxxx.yyyyyy.zzzzzz

Here the X’s represent the header, the Y’s represents the payload, and the Z’s represents the signature

An example token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiQXJpbmRhbSIsImlhdCI6MTUxNjIzOTAyMn0._RETfYt1NardpwbQDm7z-t3Ri3ltOib8Vd6tCfyG5Nk

  1. Header: It's a JSON object that contains information about the token, such as the algorithm used for the signature. The header is Base64Url encoded. Here’s an example:

    {
      "alg": "HS256",
      "typ": "JWT"
    }
  2. Paylod: It's a JSON object containing the main data that we are trying to send, such as the user ID, permissions, and expiration time. The payload is also Base64Url encoded. Here’s an example

    {
      "name": "Arindam",
      "iat": 1516239022
    }
  3. Signature: It is used to verify the token’s authenticity. The signature is calculated using a secret key known only to the server, and it is typically a combination of the header and the payload, encoded with the algorithm specified in the header. Here’s an example of the signature:

    HMAC256(
      base64UrlEncode(header) + "." + base64UrlEncode(payload),
      secret_key,
    );

If you want to play with JWT and put these concepts into practice, you can use jwt.ioDebugger to decode, verify, and generate JWTs.

How does it work?

In authentication, when the user successfully logs in using their credentials, it returns a JSON Web Token.

💡
Note: Don't store sensitive session data in browser storage due to lack of security.

And When the users want to access the protected route/private route, they have to send the JWT token typically in the Authorization header.

This works as a stateless authorization mechanism, where the server's protected routes will validate the JWT. If the JWT token is valid it sends the response to the client/user.

We'll understand it more in the next section by implementing this in the our Demo Project!

Project Setup:

1. Create Node.js Project:

To start our project, we need to set up a Node.js environment. So, let's create a node project. Run the Following command in the Terminal.

npm init

This will initialize a new Node.js project.

2. Install Dependencies:

Now, We'll install the required dependencies of our project.

npm install express mongoose jsonwebtoken dotenv

This will install the following packages:

  • express: a popular web framework for Node.js
  • mongoose: An ODM (Object Data Modeling) library for MongoDB.
  • jsonwebtoken: For generating and verifying JSON Web Tokens (JWT) for authentication.
  • dotenv: loads environment variables from a .env file

3. Setup Environment Variables:

Next, we'll create a .env folder to securely store our sensitive information such as API credentials.

//.env
MONGODB_URL=<Your MongoDB Connection String>
JWT_SECRET= "your_secret_key_here"
PORT=3000

4. Creating a MongoDB Database:

First to MongoDB Atlas and create an account or sign-in if you already have one.

Then we'll Get this Interface:

MongoDb Atlas

Then we'll Click on Build a Database, Here we'll get these options.

For this project we'll be using M0 that's the free tier.

Pricing Page

After selecting the Tier and the Provider we'll click button. Then we'll get something like this:

Pricing page (2)

Here we'll set the username and Password. (Make sure to remember the Password)

Then Scroll a bit and set the IP to 0.0.0.0/0 and Everywhere. This means Our database can be acessible from everywhere.

Pricing Page (3)

Now we'll click the Finish button. It will initiate the deployment of our cluster.

And,We'll get something like this:

Notification

After that We'll get a dashboard like this:

Dashboard UI

In this Home page of the cluster,we'll click on connect button.

Connect to Cluster Popup

Then, the following window will appear and then we'll click on the Compass link.

After that, we'll get something like this:

Connection string Pop up

If you haven't installed MongoDB Compass you have to install it first. I've installed it in my machine so I have selected that.

Now We'll copy the connection string.

In my case it's mongodb+srv://<username>:<password>@cluster0.io7vzwv.mongodb.net/ . Replace the username & password with your own username & password.

Now we'll Open MogoDb Compass and paste the connection string to connect with our cluster.

After Connecting we'll get this interface!

MongoDb Compass

Awesome! we have successfully created a MongoDb database.

5. Create Express Server:

Now, we'll create a index.js file in the root directory and set up a basic express server. See the following code:

const express = require("express");
const dotenv = require("dotenv");
const mongoose = require("mongoose");

dotenv.config();

const app = express();
const port = process.env.PORT;

//middleware provided by Express to parse incoming JSON requests.
app.use(express.json());

mongoose.connect(process.env.MONGODB_URL).then(() => {
  console.log("MongoDB is connected!");
});

app.get("/", (req, res) => {
  res.send("Hello World");
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Here, We're using the "dotenv" package to access the PORT number from the .env file.

At the top of the project, we're loading environment variables using dotenv.config() to make it accessible throughout the file.

6. Run Project:

In this step, we'll add a start script to the package.json file to easily run our project.

By using the command node index.js, we have to restart your server each time when you make changes to your file. To avoid this we can install nodemon using the following command.

npm install nodemon

So, Add the following script to the package.json file.

"scripts": {
  "start": "nodemon index.js"
}

The package.json file should look like this:

Image of Package.json file

To check whether everything is working or not, let's run the project using the following command:

npm run start

This will start the Express server. Now if we go to this URL http://localhost:3000/ we'll get this:

Terminal Image after running the server

Image of http://localhost:3000/

Awesome! The Project setup is done and it's working perfectly. Next up, we'll add Gemini to our project in the next section

Authentication with JWT:

1. Create User Model:

Till now we have done the basic project setup now we'll Create a new directory named “models” in our root directory and inside it, create a new file named User.js.

const mongoose = require("mongoose");

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    unique: true,
  },
  password: {
    type: String,
    required: true,
  },
});

module.exports = mongoose.model("User", userSchema);

Here we have created a simple schema for our project.

2. Create Authentication Routes:

Next up, we'll create authentication Route for users to sign up and login.

const express = require("express");
const jwt = require("jsonwebtoken");
const User = require("../models/User");
const router = express.Router();

// Signup route
router.post("/signup", async (req, res) => {
  try {
    const { username, password } = req.body;
    const user = new User({ username, password });
    await user.save();
    res.status(201).json({ message: "New user registered successfully" });
  } catch (error) {
    res.status(500).json({ message: "Internal server error" });
  }
});

// Login route
router.post("/login", async (req, res) => {
  const { username, password } = req.body;
  try {
    const user = await User.findOne({ username });

    if (!user) {
      return res.status(401).json({ message: "Invalid username or password" });
    }
    if (user.password !== password) {
      return res.status(401).json({ message: "Invalid username or password" });
    }
    // Generate JWT token
    const token = jwt.sign(
      { id: user._id, username: user.username },
      process.env.JWT_SECRET,
    );
    res.json({ token });
  } catch (error) {
    res.status(500).json({ message: "Internal server error" });
  }
});

module.exports = router;

Here, the signup route allows users to register by providing a username and password. Upon receiving the data, it creates a new user instance and saves it to the database.

And, the login route verifies the user's credentials against the database. If the username and password match, it generates a JWT token and returns it to the client.

3. Add Middleware for JWT Verification:

Here we have added one middleware function verifyJWT that verifies the JWT token sent by the client and authenticate the user.

const jwt = require("jsonwebtoken");

function verifyJWT(req, res, next) {
  const token = req.headers["authorization"];
  if (!token) {
    return res.status(401).json({ message: "Access denied" });
  }

  jwt.verify(token, process.env.JWT_SECRET, (err, data) => {
    if (err) {
      return res.status(401).json({ message: "Failed to authenticate token" });
    }
    req.user = data;
    next();
  });
}

module.exports = verifyJWT;

4. Update index.js File:

Finally, We'll integrate these authentication routes with our Express application in the index.js file.

const express = require("express");
const dotenv = require("dotenv");
const mongoose = require("mongoose");
const userMiddleware = require("./middleware/user");
const authRouter = require("./routes/auth");

dotenv.config();

const app = express();
const port = process.env.PORT;

//middleware provided by Express to parse incoming JSON requests.
app.use(express.json());

mongoose.connect(process.env.MONGODB_URL).then(() => {
  console.log("MongoDB is connected!");
});

app.use("/auth", authRouter);

app.get("/protected", userMiddleware, (req, res) => {
  const { username } = req.user;
  res.send(`This is a Protected Route. Welcome ${username}`);
});

app.get("/", (req, res) => {
  res.send("Hello World");
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Here We have created a /protected route which uses userMiddleware to checks if the user is authenticated.

If the user is authenticated , the server sends back a response saying "This is a Protected Route. Welcome [username]".

Here, [username] is replaced with the username of the authenticated user.

5. Testing the Endpoints:

Signup Endpoint:

To validate the SignUp Endpoint, we'll make a POST Request to this route http://localhost:3000/auth/signup with Headers Content-Type : application/json and the following JSON body:

{
    "username": "Arindam",
    "password": "071227"
}

Sign up route

Awesome Our Signup Endpoint is working perfectly.

Log In Endpoint:

To validate the LogIn Endpoint, we'll make a POST Request to this route http://localhost:3000/auth/login with Headers Content-Type : application/json and the following JSON body:

{
    "username": "Arindam",
    "password": "071227"
}

Login Route Request

And It's working perfectly! We got the token back as a response. We will copy the token as we'll need this token in the next section.

Testing Protected Route with JWT Token:

Now that we have obtained a JWT token from the login endpoint, we will test the protected route by including this token in the request headers.

For that, We'll Make a GET request to the protected route URL http://localhost:3000/protected . And In the request headers,we'll include the JWT token obtained from the login endpoint:

/protected rote GET request

Yay! It's Working Perfectly!!

We have successfully implemented JWT Authentication in NodeJS!

Now you can easily follow these step to authenticate your NodeJS application!

For More references You can check the following resources as well

Conclusion:

If you found this blog post helpful, please consider sharing it with others who might benefit. You can also follow me for more content on Javascript, React, and other web Development topics.

For Paid collaboration mail me at : arindammajumder2020@gmail.com

Connect with me on Twitter, LinkedIn, Youtube and GitHub.

Thank you for Reading :)

Thank You Image