Access Control and AuthorizationLesson 5.5
Implementing middleware-based authorization in Express
authorization middleware chain, authentication vs authorization, centralized vs route-level enforcement, route guard pattern, middleware composition, error handling in auth middleware
Authorization Middleware in Express
Authentication confirms who you are. Authorization confirms what you're allowed to do. These are separate concerns and should be separate middleware functions composed in sequence.
Middleware Composition Pattern
// 1. Authentication middleware — verifies token, attaches user to req
function authenticate(req, res, next) {
const token = req.cookies.token;
if (!token) return res.status(401).json({ error: 'Authentication required' });
try {
req.user = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] });
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
}
// 2. Authorization middleware factory — checks role
function requireRole(...roles) {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
// 3. Ownership middleware — checks resource belongs to user
function requireOwnership(Model) {
return async (req, res, next) => {
const resource = await Model.findById(req.params.id);
if (!resource) return res.status(404).json({ error: 'Not found' });
if (resource.userId.toString() !== req.user.id) {
return res.status(403).json({ error: 'Forbidden' });
}
req.resource = resource; // Attach for route handler
next();
};
}
// Compose as needed
app.delete('/posts/:id', authenticate, requireRole('admin', 'editor'), deletePost);
app.get('/invoices/:id', authenticate, requireOwnership(Invoice), getInvoice);Always Authenticate Before Authorizing
Authorization middleware depends on req.user being set. Ensure authenticate always runs before any authorization check. Use router-level middleware for groups of routes to avoid forgetting it on individual routes.
