9 Steps for JWT Authentication in Node.js Application
Tags: webdev, node, jwt
Published: 2024-01-29 13:54:04

Authentication is an important part of web development. JSON Web Tokens (JWT) have become a popular method for implementing authentication in web applications due to their simplicity, security, and scalability. In this post, I’ll guide you through implementing JWT authentication in a Node.js application using MongoDB for data storage.

Before starting, I’m assuming that you have Node.js, MongoDB, and VS Code installed on your machine and that you know how to create a MongoDB database and basic RESTful API.

If you haven’t yet, then you can check the following:

- [Installing Node.js](https://shefali.dev/getting-started-with-node-js/#Installing_Nodejs)
- [Download MongoDB](https://www.mongodb.com/try/download/community)
- [Download VS Code](https://code.visualstudio.com/download)
- [Creating a MongoDB database](https://shefali.dev/restful-api/#Step_4_Creating_a_MongoDB_Database)
- [Basic RESTful API](https://shefali.dev/restful-api/)

Let’s jump right into it!🚀

## What is JWT Authentication?

JWT authentication relies on JSON Web Tokens to confirm the identity of users in a web app. A JSON Web Token is an encoded JSON object that is digitally signed using a secret key pair.

In simple words, JWT authentication is like having a secret passcode for a website. Once you’re logged in, you get this passcode.

A JSON Web Token consists of three parts separated by dots (`.`):

- Header
- Payload
- Signature
Here’s the basic structure of a JWT:

```
xxxx.yyyy.zzzz
```
- **Header**: This part has info about the token, like its type and how it’s secured.
- **Payload**: This part contains claims about the user, like their username or role.
- **Signature**: Ensures the integrity of the token and verifies that it hasn’t been changed, which keeps the code safe from being messed with.
So, when you log in, you get this code. Every time you want to access something, you show this code to prove it’s you. The system checks the code, sees it’s valid, and lets you in!

Read more about [JSON Web Token](https://jwt.io/introduction).

Now, let’s see the steps of JWT authentication in your node.js project.

## Step1: Setting Up the Project

First, create a new directory for your project and navigate into it by using the following commands one by one.

```
mkdir nodejs-jwt-auth
cd nodejs-jwt-auth
```
Initialize the project by running the following command in the terminal (make sure you’re in your newly created project folder).

```
npm init -y
```
If you want more details about the above command, then you can [click here](https://shefali.dev/restful-api/#Step_1_Setting_Up_the_Project).

Next, install the necessary dependencies by the following command:

```
npm install express mongoose jsonwebtoken dotenv
```
The above command will install

- express: For building the web server.
- mongoose: An ODM (Object Data Modeling) library for MongoDB.
- jsonwebtoken: For generating and verifying JSON Web Tokens (JWT) for authentication.
- dotenv: For loading environment variables from a `.env` file.

To this point, your `package.json` file should look like this:

![package.json](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p4v5pnlzmlyh9vye85bc.png)

## Step 2: Getting the MongoDB Connection String

To get the MongoDB connection string, check the following link. I’ve explained it step by step over there.

[Getting the MongoDB Connection String](https://shefali.dev/restful-api/#Step_4_Creating_a_MongoDB_Database)

## Step 3: Creating a `.env` file

To protect our MongoDB connection string, let’s create a new file named `.env` in the root directory.

Add the following code to the `.env` file.

```
MONGODB_URL=<Your MongoDB Connection String>
SECRET_KEY="your_secret_key_here"
```

Replace `<Your MongoDB Connection String>` with the connection string you obtained from MongoDB Atlas (in step 2) and `your_secret_key_here` with your desired secret message. Now your `.env` file should look like this.

```
MONGODB_URL='mongodb+srv://shefali:********@cluster0.sscvg.mongodb.net/nodejs-jwt-auth'
SECRET_KEY="ThisIsMySecretKey"
```

In the MONGODB_URL, at last, I’ve added `nodejs-jwt-auth` which is our database name.

## Step 4: Set Up the Express app

Create a file named `index.js` in your root directory and add the following code to this file.

```js
const express = require("express");
const mongoose = require("mongoose");

require("dotenv").config(); //for using variables from .env file.

const app = express();
const port = 3000;

//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 is listening on port ${port}`);
});
```
Note: If you don’t get how this above code is written, then you can check [Build a RESTful API with Node.js, MongoDB, and Express](https://shefali.dev/restful-api/). Over there I’ve explained it in detail.

Now, let’s run the server by the following command.

```
node index.js
```
The output should be like the following image.

![node index.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/461xoa61ntl2ikudq6ed.png)

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

```
npm install -g nodemon
```
Now run your server using the below command, it restarts the server automatically each time you make the changes to your file.

```
nodemon index.js
```

## Step 5: Creating a User Model

Create a new directory named “models” in your root directory and inside it, create a new file named “User.js”.

![JWT Authentication with Node.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdakhhhige037jgn8d02.png)

Now, let’s create a simple schema for our project. (Know more about [Schemas](https://mongoosejs.com/docs/guide.html))

Add the following code to the `User.js` file.

```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);
```

## Step 6: Implementing Authentication Routes

Inside your root directory, create a new directory named “routes” and inside it, create a file named “auth.js”.

![JWT Authentication with Node.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o5eh4ygbd3162yhoiiq2.png)
Now, add the following code to this file:

```js
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.SECRET_KEY
    );
    res.json({ token });
  } catch (error) {
    res.status(500).json({ message: "Internal server error" });
  }
});

module.exports = router;
```

### Breaking down the above code:
**Importing Dependencies:**

```js
const express = require("express");
const jwt = require("jsonwebtoken");
const User = require("../models/User");
const router = express.Router();
```
Here, we are importing the following dependencies:

- **express**: For building the web server.
- **jsonwebtoken**: For generating and verifying JSON Web Tokens (JWT) for authentication.
- **User**: Model imported from the User module, which we have created in step 5.
- **router**: The Router() function from Express is used to define routes separately and then later combined into the main application.

**Signup Route:**

```js
// 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" });
  }
});
```

- This route listens for POST requests to `/signup`.
- When a request is received, it extracts the username and password from the request body.
- It then creates a new instance of the `User` model with the provided username and password.
- The `save()` method is called to save the new user to the database. (Learn about more [Mongoose methods](https://mongoosejs.com/docs/documents.html))
- If the user is successfully saved, it responds with a status code of 201 and a JSON message indicating “New user registered successfully”.
- If an error occurs during the process, it catches the error and responds with a status code of 500 and the error message “Internal Server Error”.

**Login Route:**

```js
// 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.SECRET_KEY
    );
    res.json({ token });
  } catch (error) {
    res.status(500).json({ message: "Internal server error" });
  }
});
```

- This route listens for POST requests to `/login`.
- When a request is received, it extracts the username and password from the request body.
- It then searches for a user in the database with the provided username.
- If no user is found, it responds with a status code of 401 (Unauthorized) and a JSON message indicating invalid username or password.
- If a user is found, it checks if the provided password matches the stored password in the database.
- If the password does not match, it responds with a status code of 401 (Unauthorized) and a JSON message indicating invalid username or password.
- If the password matches, it generates a JWT using `jwt.sign()` with the user’s ID and username as the payload and a secret key.
- The generated token is then sent as a JSON response.
- If there’s an error during the process, it catches the error and responds with a status code of 500 and the error message “Internal Server Error”.

**Exporting Router:**

```js
module.exports = router;
```

Finally, the router is exported to be used in the `index.js` file.

## Step 7: Securing Routes with Middleware

In the root directory, create a new file named `middleware.js` and add the following code to this file.

```js
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.SECRET_KEY, (err, data) => {
    if (err) {
      return res.status(401).json({ message: "Failed to authenticate token" });
    }
    req.user = data;
    next();
  });
}

module.exports = verifyJWT;
```

This code is a middleware function for verifying JSON Web Tokens (JWT) in your application.

**Breaking down the above code:**

- In the first line, we import the `jsonwebtoken` library.
- Then the verifyJWT middleware function is defined, which takes three parameters: req (request object), res (response object), and next (next middleware function).
- Inside the middleware function, it first extracts the token from the request headers.
- If there’s no token present in the request headers, it returns a 401 (Unauthorized) status along with a JSON response indicating “Access denied”.
- If a token is present, it tries to verify it using the `jwt.verify()`. 
- If verification fails, it catches the error and returns a 401 status with a JSON response indicating “Failed to authenticate token”.
- If the token is successfully verified, it attaches the decoded token data to the `req.user` object.
- Finally, the verifyJWT function is exported so that it can be used as middleware in other parts of the application.

## Step 8: Decode Details

Now to decode the details, modify the `index.js` as follows:

```js
const express = require('express');
const authRouter = require('./routes/auth');
const mongoose = require("mongoose");
const verifyJWT = require("./middleware")

require("dotenv").config(); //for using variables from .env file.

const app = express();
const PORT = 3000;

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

//Authentication route
app.use('/auth', authRouter);

//decodeDetails Route
app.get('/decodeDetails', verifyJWT, (req, res) => {
  const { username } = req.user;
  res.json({ username });
});

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

In the above code,

```js
app.use('/auth', authRouter);
```

The `/auth` route is handled by the authRouter, which contains endpoints for user authentication, such as login and signup.

```js
app.get('/decodeDetails', verifyJWT, (req, res) => {
  const { username } = req.user;
  res.json({ username });
});
```

- When a request is made to `/decodeDetails`, the verifyJWT middleware verifies the JWT token attached to the request.
- If the token is valid, the middleware extracts the username from the decoded token data stored in `req.user`.
- Finally, the route handler sends a JSON response containing the username extracted from the token.

## Step9: Testing the API

For testing the API, I’m using the VS Code extension named **Thunder Client**.

### Signup

Send a POST request to `http://localhost:3000/auth/signup` with Headers `Content-Type : application/json` and the following JSON body:

```js
{
    "username": "shefali",
    "password": "12345678"
}
```

![JWT Authentication](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tm39fgndjuvo12wev4jc.png)

In the response, you’ll see the message “New user registered successfully”.

### Login

Send a POST request to `http://localhost:3000/auth/login` with Headers `Content-Type : application/json` and the JSON body with the username and password, which you created in the signup route.

```js
{
    "username": "shefali",
    "password": "12345678"
}
```

![JWT Authentication](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nq5qhax8gn5mbo9wjzwn.png)

In the response, you’ll receive a token. Note down this token as you’ll need this while testing the decodeDetails route.

### decodeDetails

Send a GET request to `http://localhost:3000/decodeDetails` with an Authorization header with the value of the token (You got it while testing the login route).

![JWT Authentication](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qy0wpzsp0z0t1vre2gq.png)

In the response, you’ll get the username.

Congratulations! 🎉

You’ve successfully implemented JWT authentication in a Node.js application. This approach provides a secure and efficient way to authenticate users in your web applications.

That’s all for today.

I hope it was helpful.

Thanks for reading.

For more content like this, [click here](https://shefali.dev/blog).

You can also follow me on [X(Twitter)](https://twitter.com/Shefali__J) for getting daily tips on web development.

Keep Coding!!
<a href="https://www.buymeacoffee.com/devshefali" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"></a>