Back to Blog
Node.js13 min read

JWT Authentication in Express.js and Node.js: Complete Guide

JSON Web Tokens (JWT) are a popular method for implementing authentication in REST APIs. In this guide, we'll implement a complete JWT authentication system using Express.js, including user registration, login, password hashing with bcrypt, and protected routes.

JSON Web Tokens (JWT) are a popular method for implementing authentication in REST APIs. In this guide, we'll implement a complete JWT authentication system using Express.js, including user registration, login, password hashing with bcrypt, and protected routes.

Installation

npm install jsonwebtoken bcrypt express
npm install --save-dev @types/jsonwebtoken @types/bcrypt

User Registration with Password Hashing

Here's how to implement user registration with bcrypt password hashing:

const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const { User } = require("../models/index");

class AuthController {
  async register(req, res) {
    try {
      const { name, email, password, role = "staff" } = req.body;

      // Validate input
      if (!name || !email || !password) {
        return res.status(400).json({
          success: false,
          message: "Name, email, and password are required",
        });
      }

      // Check if user already exists
      const existingUser = await User.findOne({ where: { email } });
      if (existingUser) {
        return res.status(409).json({
          success: false,
          message: "User with this email already exists",
        });
      }

      // Hash password with bcrypt (10 rounds)
      const hashedPassword = await bcrypt.hash(password, 10);

      // Create user
      const user = await User.create({
        name,
        email,
        password: hashedPassword,
        role,
        status: "active",
      });

      // Generate JWT token
      const token = jwt.sign(
        { id: user.id, email: user.email, role: user.role },
        process.env.JWT_SECRET || "your-secret-key-change-this",
        { expiresIn: "7d" }
      );

      return res.status(201).json({
        success: true,
        message: "User registered successfully",
        data: {
          id: user.id,
          name: user.name,
          email: user.email,
          role: user.role,
          token,
        },
      });
    } catch (error) {
      console.error("Error registering user:", error);
      return res.status(500).json({
        success: false,
        message: "Error registering user",
        error: error.message,
      });
    }
  }
}

User Login

Implementing user login with password verification:

async login(req, res) {
  try {
    const { email, password } = req.body;

    // Validate input
    if (!email || !password) {
      return res.status(400).json({
        success: false,
        message: "Email and password are required",
      });
    }

    // Find user
    const user = await User.findOne({ where: { email } });
    if (!user) {
      return res.status(401).json({
        success: false,
        message: "Invalid email or password",
      });
    }

    // Check if user is active
    if (user.status !== "active") {
      return res.status(403).json({
        success: false,
        message: "Your account has been deactivated",
      });
    }

    // Verify password with bcrypt
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      return res.status(401).json({
        success: false,
        message: "Invalid email or password",
      });
    }

    // Update last login
    await user.update({ lastLogin: new Date() });

    // Generate JWT token
    const token = jwt.sign(
      { id: user.id, email: user.email, role: user.role },
      process.env.JWT_SECRET || "your-secret-key-change-this",
      { expiresIn: "7d" }
    );

    return res.status(200).json({
      success: true,
      message: "Login successful",
      data: {
        id: user.id,
        name: user.name,
        email: user.email,
        role: user.role,
        token,
      },
    });
  } catch (error) {
    console.error("Error logging in:", error);
    return res.status(500).json({
      success: false,
      message: "Error logging in",
      error: error.message,
    });
  }
}

JWT Authentication Middleware

Creating middleware to protect routes:

const jwt = require("jsonwebtoken");

const verifyToken = (req, res, next) => {
  try {
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith("Bearer ")) {
      return res.status(401).json({
        success: false,
        message: "No token provided. Please login to access this resource.",
      });
    }

    const token = authHeader.substring(7); // Remove 'Bearer ' prefix

    // Verify token
    const decoded = jwt.verify(
      token,
      process.env.JWT_SECRET || "your-secret-key-change-this"
    );

    // Add user info to request
    req.user = decoded;
    next();
  } catch (error) {
    if (error.name === "TokenExpiredError") {
      return res.status(401).json({
        success: false,
        message: "Token has expired. Please login again.",
      });
    }

    if (error.name === "JsonWebTokenError") {
      return res.status(401).json({
        success: false,
        message: "Invalid token. Please login again.",
      });
    }

    return res.status(500).json({
      success: false,
      message: "Error verifying token",
      error: error.message,
    });
  }
};

Role-Based Access Control

Adding role-based authorization middleware:

const checkRole = (...allowedRoles) => {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({
        success: false,
        message: "Authentication required",
      });
    }

    if (!allowedRoles.includes(req.user.role)) {
      return res.status(403).json({
        success: false,
        message: "You do not have permission to access this resource",
      });
    }

    next();
  };
};

module.exports = { verifyToken, checkRole };

Using Middleware in Routes

const express = require("express");
const router = express.Router();
const productController = require("../controllers/productController");
const { verifyToken, checkRole } = require("../middleware/auth.middleware");

// All product routes require authentication
router.use(verifyToken);

// Only admin can create products
router.post("/", checkRole("admin"), productController.create);

// All authenticated users can view products
router.get("/", productController.getAll);
router.get("/:id", productController.getById);

// Only admin can update/delete
router.put("/:id", checkRole("admin"), productController.update);
router.delete("/:id", checkRole("admin"), productController.delete);

module.exports = router;

Best Practices

  • Always use a strong, unique JWT_SECRET stored in environment variables
  • Set appropriate token expiration times (7 days for most applications)
  • Never store sensitive data in JWT tokens
  • Always hash passwords with bcrypt (minimum 10 rounds)
  • Implement token refresh mechanisms for better security
  • Validate user status (active/inactive) on every request

Conclusion

JWT authentication provides a stateless, scalable solution for REST API security. Combined with bcrypt for password hashing and role-based access control, it creates a robust authentication system. Always follow security best practices and keep your JWT_SECRET secure in production environments.