Building Secure APIs in Node.js: Setting Up Protected Routes with Express
Learn how to secure your Node.js API with Express by setting up protected routes, enforcing role-based access, and using HTTP-only cookies to store JWTs. This guide covers middleware for authentication and authorization, ensuring only trusted users access sensitive routes.

Creating a secure API is crucial when building modern web applications, especially when handling sensitive user information. In this blog post, we’ll walk through setting up protected routes in Node.js and Express API using middleware for authentication and authorization. We’ll employ HTTP-only cookies to securely store JWTs (JSON Web Tokens), check cookie validity with middleware, and enforce role-based access control (RBAC).
For readers interested in learning why HTTP-only cookies are a secure choice for storing JWTs, check out my previous blog (link here).

Why Use Protected Routes?
Protected routes help secure parts of your application by ensuring that only authenticated and, in some cases, authorized users can access them. This is particularly valuable for APIs, as it helps prevent unauthorized access to sensitive data. With Express, we can easily set up middleware functions to authenticate users and restrict access to specific routes.
Setting Up the Authorize Middleware
The first middleware we’ll implement is authorize
, which verifies if a user is authenticated. This middleware will:
- Check for an access token (JWT) in the HTTP-only cookie for web clients.
- Verify the token using
jwt.verify
. - Ensure that the device making the request is trusted by cross-referencing the device information stored in the database.
Here’s the authorize
middleware:
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import { ExtendedRequest } from "../domains/user/types";
export const authorize = (req: Request, res: Response, next: NextFunction) => {
try {
let accessToken = req.cookies?.accessToken;
if (!accessToken) {
return res.status(401).json({ error: "Access token not provided" });
}
// Verify JWT
jwt.verify(
accessToken,
process.env.SECRET_KEY || "",
async (err: any, decoded: any) => {
if (err) {
return res.status(403).json({ error: "Invalid access token" });
}
// Attach decoded user data to request
(req as ExtendedRequest).user = decoded;
next();
}
);
} catch (error) {
console.error("Error in authorize middleware:", error);
res.status(500).json({ error: "Internal server error" });
}
};
Key Points of the authorize
Middleware
- Token Retrieval: For web clients, we retrieve the JWT from an HTTP-only cookie;
- JWT Verification: Using this
jwt.verify
, we check if the token is valid and hasn’t expired.
Enforcing Role-Based Access Control (RBAC)
For certain API endpoints, only specific users (like admins) should be allowed access. We can implement this with another middleware function, allowAdminsOnly
, which checks if the user has the correct role to access the route.
Here’s the allowAdminsOnly
middleware:
import { Request, Response, NextFunction } from "express";
import { ExtendedRequest } from "../domains/user/types";
import { userTypes } from "../utils/constants";
export const allowAdminsOnly = (
req: Request,
res: Response,
next: NextFunction,
) => {
try {
const user = (req as ExtendedRequest).user;
if (!user) {
return res.status(401).json({ error: "Unauthorized. Please log in." });
}
// Check if user role is ADMIN
if (user.user_role === userTypes.ADMIN) {
return next();
}
return res.status(403).json({ error: "You are not an admin" });
} catch (error) {
console.error("Error in checking admin role:", error);
res.status(500).json({ error: "Internal server error" });
}
};
Key Points of the allowAdminsOnly
Middleware
- User Verification: We retrieve the user data from
req.user
(added by theauthorize
middleware) and check if theuser_role
isADMIN
. - Access Restriction: If the user does not have an admin role, a
403 Forbidden
response is sent, indicating insufficient permissions.
Using Middleware in Routes
To protect routes, we can chain the authorize
and allowAdminsOnly
middlewares in specific endpoints. Here’s an example:
import express from "express";
import { authorize } from "./middlewares/authorize";
import { allowAdminsOnly } from "./middlewares/allowAdminsOnly";
const router = express.Router();
// Public route
router.get("/public", (req, res) => {
res.json({ message: "This is a public route, accessible by anyone." });
});
// Protected route, only accessible by authenticated users
router.get("/profile", authorize, (req, res) => {
res.json({ message: "This is a protected route, accessible by authenticated users." });
});
// Admin-only route, accessible by admins only
router.get("/admin", authorize, allowAdminsOnly, (req, res) => {
res.json({ message: "Welcome Admin! This is a secure, admin-only route." });
});
export default router;
How It Works
- Public Route (
/public
): Anyone can access this route, and it doesn’t require any authentication. - Protected Route (
/profile
): This route requires a valid access token and a trusted device. Only authenticated users can access it. - Admin-Only Route (
/admin
): This route is restricted to users with an admin role, using theallowAdminsOnly
middleware.
Error Handling and Best Practices
- Clear Error Messages: Always provide meaningful error messages (e.g., "Access token not provided" or "Invalid or inactive device") to inform users why their request is being denied.
- Token Expiration: Regularly expire and refresh tokens to enhance security.
- Logging: Use server-side logging to capture and analyze unauthorized access attempts or errors in middleware.
- Environment Variables: Use
process.env
to manage secrets securely. Never hard-code sensitive informationSECRET_KEY
directly in your code.
By setting up protected routes using the authorize
and allowAdminsOnly
middleware functions, you create a secure API that supports both authentication and role-based access control in your Express application. This approach enhances security, ensures only trusted devices have access and restricts sensitive areas of the application to authorized users.
You can check out the NodeJS page, where you will find lots of useful articles for your next project.

That's it for today. See ya 👋