When building APIs, not every route should be publicly accessible.
Some routes should only be available to authenticated users.
This is where Protected Routes and Authentication Middleware come in.
For day 39, the goal was to understand what public and protected routes are and how the middleware authenticates the user to the protected routes.
Public vs Protected Routes
Before implementing authentication, it's important to understand the difference between public and protected routes.
Public Routes
Public routes are accessible by anyone.
Examples:
POST /register
POST /login
GET /products
GET /posts
These routes do not require authentication.
Protected Routes
Protected routes are only accessible to authenticated users.
Examples:
GET /profile
POST /todos
DELETE /posts
PATCH /settings
To access these routes, the request must include a valid JWT token.
What is Authentication Middleware?
Middleware in Express runs between the request and the response.
Authentication middleware checks whether a request is authorized to access a resource.
Request Flow
Client Request
↓
Auth Middleware
↓
Controller
↓
Response
The middleware checks:
- Does the request contain a token?
- Is the token valid?
- Is the token expired?
Result
Valid token → Request continues
Invalid token → Request rejected
Extracting JWT from Request Headers
Most APIs send JWT tokens using the Authorization header.
Example request header:
Authorization: Bearer TOKEN
The middleware must extract the token from this header.
Example
const token = req.headers.authorization?.split(" ")[1];
This code uses optional chaining to avoid errors if the header is missing.
Concepts involved:
- HTTP request headers
- Bearer token format
- Optional chaining
Verifying the JWT Token
After extracting the token, the next step is verifying it.
This is done using the jsonwebtoken library.
Example
const decoded = jwt.verify(token, process.env.JWT_SECRET);
If the token is valid, it returns the decoded payload.
Example payload:
{
"userId": "123",
"iat": 1710000000,
"exp": 1710003600
}
Fields explained:
| Field | Meaning |
|---|---|
| userId | ID of the authenticated user |
| iat | Token issued time |
| exp | Token expiration time |
If the token is invalid or expired, an error is thrown.
Attaching User Data to the Request
After verifying the token, we can attach the user information to the request object.
Example:
req.user = decoded;
Now any controller can access the authenticated user.
Example:
req.user.userId
This is useful for user-specific operations, such as:
- Fetching a user's profile
- Creating user-specific todos
- Updating user settings
Handling Authentication Errors
Authentication middleware should properly handle errors.
Common cases:
| Error | Response |
|---|---|
| Token missing | 401 Unauthorized |
| Invalid token | 401 Unauthorized |
| Expired token | 401 Unauthorized |
Example response:
return res.status(401).json({
message: "Unauthorized"
});
You can also return more descriptive messages for debugging during development.
Applying Middleware to Protect Routes
Middleware can be attached to routes to protect them.
Example:
router.get("/profile", authMiddleware, getProfile);
Now the route:
GET /profile
Requires authentication.
What happens now?
Request without token → blocked
Request with valid token → allowed
Complete Protected Route Flow
Here’s the full authentication journey:
Best Practices for Auth Middleware
When implementing authentication middleware, follow these best practices:
🔐 Security
- Store secrets in
.env - Never hardcode JWT secrets
- Use strong secret keys
📦 API Design
- Use
Authorization: Bearer TOKEN - Return proper HTTP status codes
- Keep middleware reusable
🧠 Architecture
Avoid duplicating authentication logic inside controllers.
Instead, centralize it in middleware.
User Access Workflow
Here’s a simplified access flow:
NOT logged in (no JWT)
→ can access: /register, /login, /home, /about
→ blocked from protected routes ❌
Logged in (has JWT)
→ can access: /profile, /todos, /posts, /settings
→ restricted from premium features ❌
Logged in + Premium role
→ can access premium routes ✅
This is usually implemented using role-based access control (RBAC).
Complete JWT Lifecycle
The full JWT lifecycle typically looks like this:
User registers → Account stored in database
User logs in → JWT generated
JWT sent to client
Client stores token
Client sends token with API requests
Server verifies token
User accesses protected routes
JWT expires → user must log in again
Final Thoughts
Protected routes are a core concept in backend development.
By combining:
- JWT authentication
- Auth middleware
- Proper route protection
You can build secure APIs that only allow authorized users to access sensitive data.
If you're learning backend development, mastering this pattern will help you understand:
- API security
- Authentication flows
- Scalable backend architecture
Thanks for reading. Feel free to share your thoughts!

Top comments (0)