Lesson 19 / 23

JWT-based Auth

Login issues a token; a middleware guards protected routes.

The flow

  1. client sends credentials to /login. 2) server verifies and signs a JWT. 3) client sends that token on every future request. 4) a middleware verifies it before allowing access.

Login route

On success, sign a token carrying the user id and an expiry.

import jwt from 'jsonwebtoken';

app.post('/login', express.json(), async (req, res) => {
  const { email, password } = req.body;
  const user = await verifyCredentials(email, password); // your own check
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });

  const token = jwt.sign({ sub: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
  res.json({ token });
});

Protecting a route

Read the Authorization: Bearer <token> header, verify it, attach the user, then continue.

function requireAuth(req, res, next) {
  const header = req.headers.authorization || '';
  const token = header.startsWith('Bearer ') ? header.slice(7) : null;
  if (!token) return res.status(401).json({ error: 'No token provided' });

  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch {
    res.status(401).json({ error: 'Invalid or expired token' });
  }
}

app.get('/me', requireAuth, (req, res) => {
  res.json({ userId: req.user.sub });
});

Keep the secret secret

Never hardcode JWT_SECRET — load it from an environment variable, and always serve auth traffic over HTTPS.