Paramclasses Academy - Blog

Tutorials Details

←
Post 28 of 40
→

JWT Authentication in Node.js: Access Tokens and Protected Routes

JWT Authentication in Node.js: Access Tokens and Protected Routes

October 30, 2026

JWT Authentication in Node.js: Access Tokens and Protected Routes

Authentication is an essential part of modern web applications. Whether you are developing an e-commerce website, school management system, CRM, admin panel, or REST API, you need a secure way to identify users and control access to private information.

In Node.js applications, JSON Web Token (JWT) is a popular method for implementing token-based authentication. It allows users to log in and access protected API routes without sending their email and password with every request.

In this practical tutorial, you will learn how JWT authentication works in Node.js using Express.js, MySQL, bcrypt, jsonwebtoken, and dotenv.

What Is JWT Authentication?

JWT stands for JSON Web Token. It is a compact token format used to transfer information between two parties.

After a user successfully logs in, the server creates a JWT and sends it to the client. The client then includes that token in the Authorization header when requesting protected API endpoints.

A JWT commonly contains three parts:

  1. Header – specifies the token type and signing algorithm.

  2. Payload – contains information such as user ID and email.

  3. Signature – helps verify that the token has not been modified.

Important: A JWT payload is encoded, not encrypted. Never store passwords, secret keys, or sensitive personal information inside it.

How JWT Authentication Works

The typical authentication process follows these steps:

  1. A user enters their email and password.

  2. The Node.js server finds the user in the MySQL database.

  3. The server compares the submitted password with the stored bcrypt hash.

  4. If the credentials are correct, the server generates a JWT access token.

  5. The client stores the token securely.

  6. The client sends the token with future API requests.

  7. Authentication middleware verifies the token.

  8. If the token is valid, the protected route is accessed.

Technologies Required

We will use:

  • Node.js – JavaScript runtime.

  • Express.js – web framework.

  • MySQL – database.

  • mysql2 – MySQL database connection.

  • jsonwebtoken – generate and verify JWT tokens.

  • bcrypt – password hashing and comparison.

  • dotenv – manage environment variables.

Step 1: Create a Node.js Project

Create a new folder named jwt-authentication.

Open the terminal inside this folder and run:

npm init -y

Install the required packages:

npm install express mysql2 jsonwebtoken bcrypt dotenv

Your project structure will look like this:

jwt-authentication/
│
├── config/
│   └── db.js
│
├── middleware/
│   └── authMiddleware.js
│
├── .env
├── server.js
└── package.json

Step 2: Create the MySQL Database

Open phpMyAdmin or MySQL and execute:

CREATE DATABASE auth_db;

USE auth_db;

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(150) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

This table stores user details. The password column will contain a bcrypt hash, not the original password.

You can use the same auth_db.users table created in the previous authentication tutorial.

Step 3: Configure Environment Variables

Create a .env file in the project root:

PORT=3000

DB_HOST=localhost
DB_USER=root
DB_PASSWORD=
DB_NAME=auth_db

JWT_SECRET=replace_this_with_a_long_random_secret
JWT_EXPIRES_IN=15m

Security note: Replace the example JWT secret with a long, randomly generated secret. Never publish your actual .env file or upload it to GitHub.

Add .env to your .gitignore file:

node_modules/
.env

Step 4: Create the MySQL Connection

Create config/db.js:

const mysql = require("mysql2/promise");

const pool = mysql.createPool({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    waitForConnections: true,
    connectionLimit: 10
});

module.exports = pool;

We are using a connection pool so the application can reuse database connections.

Step 5: Create JWT Authentication Middleware

Create middleware/authMiddleware.js:

const jwt = require("jsonwebtoken");

function authenticateToken(req, res, next) {

    const authHeader = req.headers.authorization;

    // Check Authorization header
    if (!authHeader) {
        return res.status(401).json({
            message: "Access token is required"
        });
    }

    // Expected format: Bearer TOKEN
    const parts = authHeader.split(" ");

    if (parts.length !== 2 || parts[0] !== "Bearer") {
        return res.status(401).json({
            message: "Invalid authorization format"
        });
    }

    const token = parts[1];

    // Verify JWT token
    jwt.verify(token, process.env.JWT_SECRET, (err, user) => {

        if (err) {
            return res.status(403).json({
                message: "Invalid or expired token"
            });
        }

        req.user = user;

        next();
    });
}

module.exports = authenticateToken;

Understanding the middleware:

  • req.headers.authorization reads the Authorization header.

  • Bearer identifies the token authentication scheme.

  • jwt.verify() checks the token signature and expiration.

  • req.user makes the verified token payload available to the next middleware or route.

  • next() allows the request to continue.

If verification fails, the protected route will not execute.

Step 6: Create the Main Server and Login API

Create server.js:

require("dotenv").config();

const express = require("express");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");

const pool = require("./config/db");
const authenticateToken = require("./middleware/authMiddleware");

const app = express();

app.use(express.json());

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

    try {

        const { email, password } = req.body;

        if (!email || !password) {
            return res.status(400).json({
                message: "Email and password are required"
            });
        }

        // Find user in database
        const [users] = await pool.execute(
            "SELECT id, name, email, password FROM users WHERE email = ?",
            [email]
        );

        if (users.length === 0) {
            return res.status(401).json({
                message: "Invalid email or password"
            });
        }

        const user = users[0];

        // Compare submitted password with stored hash
        const isMatch = await bcrypt.compare(
            password,
            user.password
        );

        if (!isMatch) {
            return res.status(401).json({
                message: "Invalid email or password"
            });
        }

        // Generate JWT access token
        const token = jwt.sign(
            {
                id: user.id,
                email: user.email
            },
            process.env.JWT_SECRET,
            {
                expiresIn: process.env.JWT_EXPIRES_IN || "15m"
            }
        );

        res.json({
            message: "Login successful",
            token: token,
            user: {
                id: user.id,
                name: user.name,
                email: user.email
            }
        });

    } catch (error) {

        console.error(error);

        res.status(500).json({
            message: "Internal server error"
        });
    }
});


// Protected Profile API
app.get("/api/profile", authenticateToken, async (req, res) => {

    try {

        const [users] = await pool.execute(
            "SELECT id, name, email, created_at FROM users WHERE id = ?",
            [req.user.id]
        );

        if (users.length === 0) {
            return res.status(404).json({
                message: "User not found"
            });
        }

        res.json({
            message: "Protected profile accessed successfully",
            user: users[0]
        });

    } catch (error) {

        console.error(error);

        res.status(500).json({
            message: "Internal server error"
        });
    }
});


// Server start
const PORT = process.env.PORT || 3000;

async function startServer() {

    try {

        await pool.query("SELECT 1");

        console.log("MySQL connected successfully");

        app.listen(PORT, () => {
            console.log(`Server running at http://localhost:${PORT}`);
        });

    } catch (error) {

        console.error("Database connection failed:", error.message);

        process.exit(1);
    }
}

startServer();

Step 7: Run Your Node.js Application

Open your terminal and run:

node server.js

If everything is configured correctly, you should see:

MySQL connected successfully
Server running at http://localhost:3000

Step 8: Test JWT Login Using Postman

Open Postman and send a POST request:

URL:

http://localhost:3000/api/login

Select Body → raw → JSON and enter:

{
    "email": "student@example.com",
    "password": "123456"
}

Use an email and password belonging to an existing database user. The stored password must be a bcrypt hash.

A successful response will look like:

{
    "message": "Login successful",
    "token": "YOUR_JWT_ACCESS_TOKEN",
    "user": {
        "id": 1,
        "name": "Rahul",
        "email": "student@example.com"
    }
}

Copy the returned token.

Step 9: Access the Protected API

Now send a GET request:

http://localhost:3000/api/profile

In Postman, open the Authorization tab:

  • Type: Bearer Token

  • Token: Paste the token received from the login API.

Click Send.

A successful response will look like:

{
    "message": "Protected profile accessed successfully",
    "user": {
        "id": 1,
        "name": "Rahul",
        "email": "student@example.com",
        "created_at": "2026-10-30T10:00:00.000Z"
    }
}

The date and time shown will depend on your database record.

Try accessing the same endpoint without a token. The server should return HTTP 401. An invalid or expired token should return HTTP 403.

What Are Access Tokens and Refresh Tokens?

An access token is a short-lived token used to access protected APIs.

A refresh token is a separate, longer-lived credential that can be used to obtain a new access token without asking the user to log in again.

For beginner projects, start by understanding access tokens. A production refresh-token system needs additional security, including secure storage, rotation, expiration, and revocation.

Important JWT Security Best Practices

Follow these practices when building real-world applications:

  1. Use a strong and private JWT secret.

  2. Set a short access-token expiration time.

  3. Always use HTTPS in production.

  4. Never include passwords or sensitive information in JWT payloads.

  5. Store passwords using bcrypt or another suitable password-hashing algorithm.

  6. Validate incoming request data.

  7. Do not expose database errors or secret values in API responses.

  8. Keep dependencies updated.

  9. Use secure token storage on the client side.

  10. Remember that a valid JWT generally remains usable until it expires unless you implement a revocation mechanism.

JWT authentication is not the same as automatic logout. Logging out on the client by deleting a token does not invalidate a copied token on the server. Short expiration times, token denylisting, or a properly designed refresh-token system may be needed for stronger session control.

Common JWT Authentication Errors

1. JsonWebTokenError

The token may be invalid, incorrectly copied, or signed with a different secret.

2. TokenExpiredError

The access token has expired. The user needs to authenticate again or use a properly implemented refresh-token flow.

3. Access token is required

The request does not contain the Authorization header.

4. Invalid authorization format

Use this format:

Authorization: Bearer YOUR_TOKEN

5. Database connection error

Check your MySQL username, password, database name, and server status.

Frequently Asked Questions

Q1. What is JWT in Node.js?

JWT is a token-based authentication method used to verify users and protect API endpoints in Node.js applications.

Q2. What is the use of jsonwebtoken?

The jsonwebtoken package provides methods such as jwt.sign() to create tokens and jwt.verify() to validate them.

Q3. Is JWT more secure than session authentication?

Neither approach is automatically more secure. Security depends on implementation, token or session storage, expiration, transport security, and other protections.

Q4. Can we use JWT with MySQL?

Yes. You can store user information and bcrypt password hashes in MySQL, then use JWT to authenticate API requests.

Q5. Can JWT tokens expire?

Yes. You can configure token expiration using the expiresIn option when generating a JWT.

Conclusion

JWT authentication is an important skill for Node.js backend developers. It helps you build APIs where users can log in and access protected resources using access tokens.

In this tutorial, we created a login API, generated JWT access tokens, verified tokens through Express middleware, connected the application to MySQL, and tested protected routes using Postman.

If you want to become a professional backend developer, practice this implementation and understand how each part works.

Learn Node.js, Express.js, MySQL, REST API Development, and Backend Programming with Param Classes.

←
Post 28 of 40
→