Express.js JWT Authentication with MongoDB

Securing your Express.js application is crucial, and JSON Web Tokens (JWT) paired with MongoDB provide a robust solution. This tutorial demonstrates how to integrate JWT authentication using password hashing, Mongoose, and bcryptjs. These tools ensure secure user authentication in your Node.js application.



Understanding JWT and MongoDB for Authentication

JWT authentication validates users through tokens, while MongoDB provides persistent storage for user data. Passwords are securely hashed using bcryptjs, Mongoose defines schemas, and CORS handles cross-origin requests. The jsonwebtoken package generates and authenticates tokens, dotenv secures sensitive keys, and body-parser processes incoming data. Together, these tools establish a secure authentication system for your Express.js application.

Project Setup and Folder Structure

To maintain a clean and scalable architecture, follow this folder structure:

express-jwt-mongo/
│── config/
│   └── db.js           # MongoDB connection setup
│── middleware/
│   └── authMiddleware.js  # Middleware for JWT authentication
│── models/
│   └── User.js         # Mongoose user schema
│── routes/
│   ├── auth.js         # User authentication routes (register/login)
│   ├── protected.js    # Protected route requiring authentication
│── .env                # Environment variables (JWT secret, MongoDB URI, etc.)
│── app.js (or server.js)  # Main server file
│── package.json        # Project dependencies and scripts
│── node_modules/       # Installed dependencies

Explanation of Structure:

  • config/db.js: Manages the MongoDB connection.
  • middleware/authMiddleware.js: Middleware to validate JWT tokens.
  • models/User.js: Defines the MongoDB schema for user authentication.
  • routes/auth.js: Handles user registration and login.
  • routes/protected.js: Contains protected routes requiring authentication.
  • .env: Stores sensitive environment variables securely.
  • app.js (or server.js): Main Express.js server configuration.
  • package.json: Manages dependencies and project metadata.

Step 1: Initialize the Project

Create a new project and install the required dependencies:

mkdir express-jwt-mongo
cd express-jwt-mongo
npm init -y
npm install express jsonwebtoken bcryptjs dotenv mongoose cors body-parser

Step 2: Configure Environment Variables

Create a .env file in the root directory to store sensitive information:

PORT=3000
JWT_SECRET=your-secret-key-here
MONGO_URI=mongodb://localhost:27017/jwt-auth

The last part of the URI (jwt-auth) represents the database name that MongoDB will connect to. If the database doesn't exist, MongoDB creates it automatically when the first data is inserted.

Step 3: Configure MongoDB Connection

Create a config/db.js file to establish a MongoDB connection.

// config/db.js

const mongoose = require('mongoose');
require('dotenv').config();

const connectDB = async () => {
    try {
        await mongoose.connect(process.env.MONGO_URI, {
            useNewUrlParser: true,
            useUnifiedTopology: true,
        });
        console.log('MongoDB connected');
    } catch (error) {
        console.error('Database connection failed:', error);
        process.exit(1);
    }
};

module.exports = connectDB;

Step 4: Create the User Model

Define a schema for user authentication in models/User.js:

// models/User.js

const mongoose = require('mongoose');

const UserSchema = new mongoose.Schema({
    name: { type: String, required: true, unique: true },
    email: { type: String, required: true, unique: true },
    password: { type: String, required: true },
}, { timestamps: true });

module.exports = mongoose.model('User', UserSchema);

Step 5: Implement User Registration and Authentication

Create routes/auth.js to handle user registration and login.

// routes/auth.js

const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
require('dotenv').config();

const router = express.Router();

// User Registration
router.post('/register', async (req, res) => {
    try {
        const { name, email, password } = req.body;

        // Check if user already exists
        let user = await User.findOne({ email });
        if (user) return res.status(400).json({ message: "User already exists" });

        // Hash password
        const salt = await bcrypt.genSalt(10);
        const hashedPassword = await bcrypt.hash(password, salt);

        // Create new user
        user = new User({ name, email, password: hashedPassword });
        await user.save();

        res.status(201).json({ message: "User registered successfully" });
    } catch (error) {
        res.status(500).json({ message: "Server error" });
    }
});

// User Login
router.post('/login', async (req, res) => {
    try {
        const { email, password } = req.body;

        // Check if user exists
        let user = await User.findOne({ email });
        if (!user) return res.status(400).json({ message: "Invalid credentials" });

        // Verify password
        const isMatch = await bcrypt.compare(password, user.password);
        if (!isMatch) return res.status(400).json({ message: "Invalid credentials" });

        // Generate JWT token
        const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });

        res.status(200).json({ token });
    } catch (error) {
        res.status(500).json({ message: "Server error" });
    }
});

module.exports = router;

Step 6: Protect Routes with JWT Middleware

Create middleware/authMiddleware.js to validate JWT tokens.

// middleware/authMiddleware.js

const jwt = require('jsonwebtoken');
require('dotenv').config();

const authMiddleware = (req, res, next) => {
    const token = req.header('Authorization');

    if (!token) return res.status(401).json({ message: "Access denied, no token provided" });

    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        req.user = decoded;
        next();
    } catch (error) {
        res.status(401).json({ message: "Invalid token" });
    }
};

module.exports = authMiddleware;

Step 7: Create a Protected Route

Add a secure route in routes/protected.js:

// routes/protected.js

const express = require('express');
const authMiddleware = require('../middleware/authMiddleware');

const router = express.Router();

// Protected route
router.get('/dashboard', authMiddleware, (req, res) => {
    res.status(200).json({ message: "Welcome to the protected dashboard!", user: req.user });
});

module.exports = router;

Step 8: Set Up the Express Server

Create app.js or server.js to configure and start the server.

// app.js

const express = require('express');
const connectDB = require('./config/db');
const authRoutes = require('./routes/auth');
const protectedRoutes = require('./routes/protected');
const cors = require('cors');
const bodyParser = require('body-parser');
require('dotenv').config();

const app = express();
connectDB();

app.use(cors());
app.use(bodyParser.json());

app.use('/api/auth', authRoutes);
app.use('/api', protectedRoutes);

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Step 9: Test the Application

Ensure MongoDB is running locally:

mongod

Then, start your Express.js server:

node app.js

Test API Endpoints Using Postman or cURL:

Register a New User

  • Endpoint: POST http://localhost:3000/api/auth/register
  • Request Body (JSON):
    {
      "name": "Alex Parker",
      "email": "[email protected]",
      "password": "password123"
    }
    
  • Expected Response:
    {
      "message": "User registered successfully"
    }
    

Log in to Get a JWT Token

  • Endpoint: POST http://localhost:3000/api/auth/login
  • Request Body (JSON):
    {
      "email": "[email protected]",
      "password": "password123"
    }
    
  • Expected Response (JWT Token):
    {
      "token": "your_jwt_token_here"
    }
    

    Copy the token for the next step.

Access a Protected Route

  • Endpoint: GET http://localhost:3000/api/dashboard
  • Headers:
    Authorization: Bearer your_jwt_token_here
    
  • Expected Response:
    {
      "message": "Welcome to the protected dashboard!",
      "user": {
        "id": "605c72ff9f1b2c001c8e4c4a",
        "iat": 1710433740,
        "exp": 1710437340
      }
    }
    

Automate API Testing with cURL (Optional)

If you prefer command-line testing, use cURL:

# Register
curl -X POST http://localhost:3000/api/auth/register -H "Content-Type: application/json" -d '{"name":"Alex Parker","email":"[email protected]","password":"password123"}'

# Log in
curl -X POST http://localhost:3000/api/auth/login -H "Content-Type: application/json" -d '{"email":"[email protected]","password":"password123"}'

# Access Protected Route (replace YOUR_TOKEN)
curl -X GET http://localhost:3000/api/dashboard -H "Authorization: Bearer YOUR_TOKEN"

Conclusion

You have successfully implemented JWT authentication in an Express.js application using MongoDB. This authentication system ensures secure access control in a stateless manner, suitable for modern web applications.



Found This Page Useful? Share It!
Get the Latest Tutorials and Updates
Join us on Telegram

Keep W3schools Growing with Your Support!
❤️ Support W3schools