Hello! In our last lesson, we successfully built registration and login endpoints that issue a JSON Web Token (JWT) upon successful authentication. We've established how to create users, securely hash their passwords, and provide them with a "key" (the JWT) when they log in.
But right now, that key doesn't unlock any doors. Our server issues the token but doesn't yet check for it on any routes. This lesson will bridge that gap. Our goal is to create protected route middleware in Express that verifies JWTs from incoming requests. This middleware will act as a digital bouncer for our API, ensuring only authenticated users can access protected resources.
The Role of Middleware in Authentication
In Express, middleware refers to functions that have access to the request object (req), the response object (res), and the next function in the application's request-response cycle. These functions can execute code, make changes to the request and response objects, and either end the request-response cycle or pass control to the next middleware function.
Think of it like an assembly line. When a request comes in, it passes through a series of middleware functions. Each one performs a task before passing it to the next. Our authentication middleware will be one of the first stops on this line.

The logic for our authentication middleware will be:
- Check for a token on the incoming request.
- If no token is found, block the request (e.g., send a
401 Unauthorizedresponse). - If a token is found, verify its signature and expiration using our secret key.
- If the token is invalid or expired, block the request (e.g., send a
403 Forbiddenresponse). - If the token is valid, attach the decoded user information (like the user ID) to the request object and pass control to the next function in the chain (the actual route handler).
Implementing the Verification Middleware
There are two common industry standards for sending a JWT from the client to the server:
- In an
HttpOnlycookie. - In the
Authorizationheader, using theBearerscheme.
In our previous lesson, we opted for the HttpOnly cookie method as it offers better protection against XSS attacks. We'll continue with that pattern, but it's crucial for your job-readiness to understand both.
Method 1: Verifying a Token from an HttpOnly Cookie
The cookie-parser middleware, which you've likely already installed, makes the cookies sent by the client available on the req.cookies object. We can create a middleware function to read and verify the token from there.
The following video from The Net Ninja provides a fantastic walkthrough of this exact process.
Node Auth Tutorial (JWT) #15 - Protecting Routes
This video, 'Protecting Routes' by The Net Ninja, demonstrates how to create a custom middleware function to protect routes. It focuses on retrieving the JWT from a cookie and using jwt.verify to validate it.
Please watch from 02:21 to 08:31. Focus on these key steps: Creating the middleware file and the basic function structure (requireAuth). Checking for the token in req.cookies. Using jwt.verify() with the token, the secret key, and a callback function. Handling the three possible outcomes: no token, an invalid token (error), or a valid token (next()).
Let's translate this into our project structure. We can create a new file, middleware/authMiddleware.js, for this logic.
middleware/authMiddleware.js
import jwt from 'jsonwebtoken';
export const protect = (req, res, next) => {
// 1. Get token from cookie
const token = req.cookies.token;
if (!token) {
// If no token, user is not authenticated
return res.status(401).json({ message: 'Not authorized, no token' });
}
try {
// 2. Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// 3. Attach user to the request object
// We assume the payload contains an 'id' field.
// We will fetch the user from DB later if needed, but for now, the ID is enough.
req.user = { id: decoded.id };
// 4. Move to the next middleware/route handler
next();
} catch (error) {
console.error(error);
// If token is not valid
res.status(403).json({ message: 'Not authorized, token failed' });
}
};
This middleware is now ready to be used as a gatekeeper for any route we want to protect.
Method 2: Verifying a Token from the Authorization Header (For Your Information)
While we are using cookies, it's essential you recognize the Bearer token pattern, which is extremely common in REST APIs, especially those consumed by mobile apps or other backend services.
In this pattern, the client sends a header like this:Authorization: Bearer <your_jwt_here>
The server-side middleware then extracts the token from this header. To see this pattern in action, the following article provides a clear, written guide.
How To Use JSON Web Tokens (JWTs) in Express.js
The article 'How To Use JSON Web Tokens (JWTs) in Express.js' from DigitalOcean provides a professional-grade explanation of creating authentication middleware. It focuses on the Authorization header method.
Read the section titled 'Verifying JWTs with Middleware'. Pay close attention to how the code extracts the token from req.headers['authorization'] by splitting the string. Notice that while the extraction method is different, the core jwt.verify() logic is exactly the same.
The key difference in code is how the token is retrieved:
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // "Bearer <token>"
The rest of the verification logic remains identical, reinforcing the core principles.
Applying the Middleware to Protect Routes
Now that we've created our protect middleware, using it is simple. You import it into your routes file and place it before the route handler function for any route you want to secure.
Let's say you want to create a new endpoint, /api/users/profile, that should only be accessible to a logged-in user to fetch their own profile data.
In your user routes file (e.g., routes/userRoutes.js):
import express from 'express';
import { protect } from '../middleware/authMiddleware.js'; // Import the middleware
import pool from '../config/db.js'; // Your DB connection
const router = express.Router();
// ... your /register and /login routes are here (they should NOT be protected)
// This is a protected route
router.get('/profile', protect, async (req, res) => {
// Because the 'protect' middleware ran successfully,
// we can now access req.user, which contains the user's ID from the token.
try {
const user = await pool.query(
'SELECT id, name, email FROM users WHERE id = $1',
[req.user.id]
);
if (user.rows.length > 0) {
res.json(user.rows[0]);
} else {
res.status(404).json({ message: 'User not found' });
}
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Server error' });
}
});
export default router;
When a request hits /api/users/profile, Express first executes our protect middleware.
- If the token is valid,
protectcallsnext(), and control passes to ourasync (req, res) => { ... }handler. - If the token is missing or invalid,
protectsends a401or403response and the request ends. The final route handler is never reached.
Test your understanding!
A developer is building an e-commerce API. They have two routes:
GET /api/products(returns a list of all products, public access)POST /api/orders(creates a new order for the logged-in user, protected access)
They write the following code:
import { protect } from '../middleware/authMiddleware.js';
// ...
router.get('/api/products', (req, res) => { /* logic to get products */ });
router.post('/api/orders', (req, res) => { /* logic to create order */ }, protect);
Why will this code fail to protect the /api/orders route? What is the correct way to write it?
Show answer
The middleware function must be placed before the final route handler, not after it. In Express, arguments are processed in order. In the incorrect code, the route handler for /api/orders will execute and likely complete before the protect middleware is ever called.
The correct implementation is:
router.post('/api/orders', protect, (req, res) => {
// This code will only run if protect() calls next()
/* logic to create order using req.user.id */
});
This ensures the "bouncer" checks the user's credentials before they are allowed to access the route's logic.
Conclusion
You have now implemented one of the most fundamental security features of a modern web application. By creating and applying this middleware, you can now definitively separate public routes from private, protected ones.
Key Takeaways:
- Middleware in Express acts as a series of checks or transformations in the request-response cycle.
- Authentication middleware is a "gatekeeper" that verifies a user's identity before allowing access to a protected resource.
- The core of the verification logic is the
jwt.verify()function, which checks a token's signature and validity against your secret key. - A valid token allows you to trust the data in its payload. By attaching this data to
req.user, you make the user's identity available to all subsequent parts of the request cycle. - Middleware is applied to a route by placing it as an argument before the final route handler function.
Preview of the Next Lesson:
We have now fully secured our backend. We can register users, log them in, issue tokens, and protect routes. The next piece of the puzzle is the frontend. How does our React application know if a user is logged in? How does it store the user's information and adapt the UI accordingly (e.g., showing a "Logout" button instead of "Login")?
In the next lesson, we will begin our dive into React and learn how to provide global authentication state to a React application using the Context API, allowing you to build a seamless user experience that reflects the authentication status managed by our backend.