Implement JWT Authentication Middleware
Protected routes require a valid access token.
Clients typically send access tokens through the Authorization header:
Authorization: Bearer <access-token>
The authentication middleware should:
- Read the Authorization header.
- Extract the Bearer token.
- Verify the token signature.
- Validate issuer, audience, algorithm, and expiration.
- Load the corresponding user.
- Attach the user to req.user.
- Continue to the next middleware.
For this implementation, we will also verify that the user still exists and is active.
Use jwt.verify(), Not jwt.decode()
Decoding a JWT only reads its payload. It does not verify authenticity. Protected APIs must verify the signature and required claims.
src/middleware/auth.middleware.js
import jwt from 'jsonwebtoken';
import { User } from '../models/user.model.js';
export async function authenticate(req, res, next) {
try {
const authorization = req.headers.authorization;
if (!authorization?.startsWith('Bearer ')) {
return res.status(401).json({
success: false,
message: 'Authentication required'
});
}
const token = authorization.slice(7);
const payload = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'],
issuer: 'rest-api',
audience: 'rest-api-client'
});
if (typeof payload.sub !== 'string') {
return res.status(401).json({
success: false,
message: 'Invalid token'
});
}
const user = await User.findById(payload.sub).select('-password');
if (!user || !user.isActive) {
return res.status(401).json({
success: false,
message: 'Invalid authentication'
});
}
req.user = user;
next();
} catch (error) {
res.status(401).json({
success: false,
message: 'Invalid or expired token'
});
}
}Extracts the Bearer token, verifies its claims, loads the user, and attaches the authenticated user to the request.
Key Takeaways
Access tokens are commonly sent through the Authorization header.
Verify JWT signatures and claims.
Load the authenticated user before protected operations.
Never trust decoded token data without verification.