DEV Community

Cover image for Day 39 of #100DaysOfCode — Protected Routes (Auth Middleware)
M Saad Ahmad
M Saad Ahmad

Posted on

Day 39 of #100DaysOfCode — Protected Routes (Auth Middleware)

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Extracting JWT from Request Headers

Most APIs send JWT tokens using the Authorization header.

Example request header:

Authorization: Bearer TOKEN
Enter fullscreen mode Exit fullscreen mode

The middleware must extract the token from this header.

Example

const token = req.headers.authorization?.split(" ")[1];
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

If the token is valid, it returns the decoded payload.

Example payload:

{
  "userId": "123",
  "iat": 1710000000,
  "exp": 1710003600
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Now any controller can access the authenticated user.

Example:

req.user.userId
Enter fullscreen mode Exit fullscreen mode

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"
});
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Now the route:

GET /profile
Enter fullscreen mode Exit fullscreen mode

Requires authentication.

What happens now?

Request without token → blocked
Request with valid token → allowed
Enter fullscreen mode Exit fullscreen mode

Complete Protected Route Flow

Here’s the full authentication journey:

authentication flow


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 ❌
Enter fullscreen mode Exit fullscreen mode
Logged in (has JWT)
→ can access: /profile, /todos, /posts, /settings
→ restricted from premium features ❌
Enter fullscreen mode Exit fullscreen mode
Logged in + Premium role
→ can access premium routes ✅
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)