Auth middleware
Right now, anyone can call every route in your API. This part adds a middleware that checks for a valid JSON Web Token before letting a request through to protected routes.
Installing a JWT library
npm install jsonwebtokenWriting the middleware
The middleware reads the Authorization header, verifies the token, and attaches the decoded payload to req for downstream routes to use:
const jwt = require('jsonwebtoken');
function requireAuth(req, res, next) {
const header = req.headers.authorization;
if (!header?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing bearer token' });
}
try {
req.user = jwt.verify(header.slice(7), process.env.JWT_SECRET);
next();
} catch {
res.status(401).json({ error: 'Invalid or expired token' });
}
}Protecting a route
Add the middleware as a second argument to any route you want to protect:
app.get('/api/users/:id', requireAuth, asyncHandler(async (req, res) => {
// req.user is now available here
const { rows } = await pool.query(
'SELECT id, name, email FROM users WHERE id = $1',
[req.params.id]
);
res.json(rows[0]);
}));This middleware only verifies a token — it doesn't issue one. A real login route that checks a password and signs a token with jwt.sign() is a natural next step once this series ends, and follows the same pattern.
Try it yourself
Generate a token manually with jwt.sign({ id: 1 }, process.env.JWT_SECRET) in a Node REPL, then call your protected route with and without an Authorization: Bearer <token> header to see both code paths. Last part: deploying this for free.
FAQ
Is a JWT encrypted?
No — a JWT is signed, not encrypted. Anyone can decode and read the payload (try pasting one into jwt.io) without knowing JWT_SECRET; the secret only proves the token wasn't tampered with. Never put a password or other sensitive value directly in a JWT payload.
What should JWT_SECRET actually be?
A long, random string — generated once with something like require('crypto').randomBytes(64).toString('hex') — not a memorable password. Anyone who obtains this value can forge a valid token for any user, so it belongs in an environment variable, never committed to source control.
Why does the catch block have no parameter (catch { ... })?
jwt.verify throws a generic error for any invalid or expired token, and this middleware doesn't need to distinguish between the specific reasons — it responds with the same 401 either way. The parameterless catch syntax is valid modern JavaScript specifically for cases where the caught error itself isn't used.