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.