~/TechPurAI
~/tutorials/build-a-rest-api/auth-middleware
intermediate·part 5 of 6·2 min read

Auth middleware

Updated Aug 31, 2026Node.js · Express · Auth

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

bash
npm install jsonwebtoken

Writing the middleware

The middleware reads the Authorization header, verifies the token, and attaches the decoded payload to req for downstream routes to use:

js
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:

js
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]);
}));
What this doesn't cover

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.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous4. Connecting a databasenext →6. Deploying for free