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/bcryptUser 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.