Complete "Lesson 5: Input Validation and Error Handling" first to unlock this lesson.Go to previous lesson

Introduction

Authentication middleware sits between incoming requests and your route handlers, verifying that users are who they claim to be before granting access to protected resources. In this lesson, you'll build JWT-based authentication middleware and apply it to specific routes, transforming your open API into a secure, production-ready system.

Middleware functions have access to the request object, response object, and the next middleware function in the stack. Authentication middleware inspects the Authorization header, validates the token, attaches user information to the request, and either passes control to the next handler or rejects the request.

Building the Authentication Middleware

Create a middleware function that extracts and verifies JWT tokens. The middleware checks for a Bearer token in the Authorization header, validates it using your secret key, and attaches the decoded payload to the request object for downstream handlers to use.

javascript
const jwt = require('jsonwebtoken');
const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
};
module.exports = authenticate;

The middleware extracts the token by removing the Bearer prefix, verifies it against your secret, and stores the decoded user data in req.user. If verification fails, it returns a 401 status without calling next, stopping the request pipeline.

flowchart showing request flowing through authentication middleware with two branches - valid token continues to route handler, invalid token returns 401 error

Protecting Routes with Middleware

Apply the authentication middleware to routes that require user identity. You can protect individual routes, groups of routes, or entire routers by inserting the middleware before your handler functions.

javascript
const express = require('express');
const authenticate = require('./middleware/authenticate');
const router = express.Router();
router.get('/profile', authenticate, (req, res) => {
res.json({
userId: req.user.id,
email: req.user.email,
message: 'This is your protected profile'
});
});
router.post('/posts', authenticate, async (req, res) => {
const post = await createPost({
...req.body,
authorId: req.user.id
});
res.status(201).json(post);
});
router.get('/public', (req, res) => {
res.json({ message: 'This endpoint is public' });
});

The authenticate middleware runs before the route handler. Routes without the middleware remain publicly accessible. The req.user object populated by the middleware gives handlers access to the authenticated user's identity.

Role-Based Access Control

Extend your middleware to handle authorization by checking user roles or permissions. Create a wrapper function that accepts required roles and returns middleware enforcing those requirements.

javascript
const authorize = (...allowedRoles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
router.delete('/users/:id', authenticate, authorize('admin'), async (req, res) => {
await deleteUser(req.params.id);
res.status(204).send();
});
router.put('/posts/:id', authenticate, authorize('admin', 'editor'), async (req, res) => {
const post = await updatePost(req.params.id, req.body);
res.json(post);
});

The authorize middleware assumes authenticate has already run and attached req.user. It checks the user's role against allowed values and returns 403 Forbidden if the role doesn't match. Chain multiple middleware functions to enforce both authentication and authorization.

Error Handling and Token Refresh

Handle common authentication scenarios gracefully. Distinguish between missing tokens, invalid tokens, and expired tokens to provide clear feedback. Consider implementing refresh tokens for long-lived sessions.

javascript
const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
error: 'No token provided',
code: 'TOKEN_MISSING'
});
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
error: 'Token expired',
code: 'TOKEN_EXPIRED'
});
}
return res.status(401).json({
error: 'Invalid token',
code: 'TOKEN_INVALID'
});
}
};

Structured error responses with error codes help clients handle different failure modes appropriately. Expired tokens might trigger a token refresh flow, while invalid tokens require re-authentication.

Summary

Authentication middleware centralizes security logic and applies it consistently across your API. By extracting and verifying JWT tokens, attaching user information to requests, and enforcing role-based access control, you transform open endpoints into a secure system. Always run authentication before authorization, return clear error responses, and consider token expiration and refresh strategies for production deployments.

Lesson practice

Lesson Checkpoint

Review what you learned and get feedback on your work.

0/1
Answered · 0/1