DevPath · Learn to code ESPTEN

Authentication and security

Authorization: require a token and check roles

Authorizing means answering "can you do this?"

Authentication says who you are; authorization decides what you are allowed to do. They are two distinct steps, and they map to two status codes:

A very common mistake is returning 401 when 403 is what's needed. If the user is already authenticated and simply lacks the permission, it's 403.

Middleware that requires a valid token

The first middleware reads the Authorization header. If it's missing —or if the token isn't valid because the signature doesn't match— it stops with 401. If it's valid, it stores the user in req.user so the next steps know who it is.

function requireAuth(req, res, next) {
  const header = req.headers.authorization;
  // verifyToken returns the payload, or null if the signature doesn't match.
  const user = header ? verifyToken(header, SECRET) : null;
  if (!user) {
    res.status(401).json({ error: "Unauthorized" });
    return; // stops: doesn't call next()
  }
  req.user = user;
  next();
}

In a real case, the header is "Bearer <token>": the scheme (Bearer) is split from the token before verifying it. For the exercises we'll send the token directly in the header to focus on the logic.

Role middleware

Role-based authorization is solved with a middleware factory: a function that receives the required role and returns the concrete middleware. This way you can write requireRole("admin") on any route.

function requireRole(role) {
  return function (req, res, next) {
    if (!req.user || req.user.role !== role) {
      res.status(403).json({ error: "Forbidden" });
      return;
    }
    next();
  };
}

They are chained in order: first requireAuth (so that req.user exists), then requireRole("admin"):

app.delete("/users/:id", requireAuth, requireRole("admin"), deleteUser);

Examples

Chaining authentication and role

function requireRole(role) {
  return function (req, res, next) {
    if (!req.user || req.user.role !== role) {
      res.status(403).json({ error: "Forbidden" });
      return;
    }
    next();
  };
}

const onlyAdmin = requireRole("admin");
const req = { user: { role: "reader" } };
const res = {
  status(c) { this.code = c; return this; },
  json(b) { console.log(this.code, JSON.stringify(b)); },
};
onlyAdmin(req, res, () => console.log("access granted"));
Put this into practice

DevPath is a hands-on course: you read the theory here; in the app you put it into practice with exercises that really run, offline.

Start free in the app →
← Authentication: passwords and tokensWeb security: OWASP, CORS and best practices →