Role-Based Authorization

Authentication asks who you are; authorization asks what you may do. Express 24,430 expresses the second as middleware between the route and its handler, and the two failures carry different codes: 401 means the credential is missing or invalid, 403 means it is valid and still not enough.

A role guard, an ownership guard, and the routes they protectJavaScript
function requireRole(...allowed) {
  return (req, res, next) => {
    if (!req.isAuthenticated()) return res.status(401).json({ error: 'not_authenticated' });
    if (!allowed.some((r) => req.user.roles.includes(r))) {                  // 403, not 401
      return res.status(403).json({ error: 'forbidden', need: allowed, have: req.user.roles });
    }
    next();
  };
}
const canEditBook = (req, res, next) =>            // roles alone cannot express ownership
  req.user.roles.includes('admin') || req.book.authorId === req.user.id
    ? next() : res.status(403).json({ error: 'not_owner' });
app.delete('/books/:id', requireRole('admin'), removeBook);
app.patch('/books/:id', requireRole('author', 'admin'), loadBook, canEditBook, updateBook);
Output
$ curl -i -b ada.jar .../profile  ->  200 OK  {"id":"u_1","email":"ada@example.com"}
$ curl -i -b ada.jar -X DELETE .../books/7       # ada has roles ["author"]
HTTP/1.1 403 Forbidden  {"error":"forbidden","need":["admin"],"have":["author"]}
$ curl -i -b root.jar -X DELETE .../books/7      # root has ["author","admin"]
HTTP/1.1 200 OK  {"deleted":"7","by":"root@example.com"}

Roles stop scaling the moment ownership matters. "An author may edit a book" is false as written — an author may edit their own book — which is why canEditBook compares the record to the caller. Returning 403 rather than 404 also tells an attacker the resource exists; where that matters, answer 404 instead.

Three rules keep the layer honest. Deny by default: mount the guard on the router (router.use(requireRole('admin'))) so a route added next year is protected anyway. Check on the server every time, because a hidden button in React 7,897 is decoration, not a control. And read roles from the session or a freshly verified token, never from anything the client controls.