javascript 77 lines · 3 tabs

Role-Based Access Control in Express with a requireRole Middleware Factory

Shared by codesnips Aug 2026
3 tabs
const jwt = require('jsonwebtoken');

function authenticate(req, res, next) {
  const header = req.headers.authorization || '';
  const [scheme, token] = header.split(' ');

  if (scheme !== 'Bearer' || !token) {
    return res.status(401).json({ error: 'Missing or malformed Authorization header' });
  }

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

function requireRole(...allowedRoles) {
  return function (req, res, next) {
    if (!req.user) {
      return res.status(401).json({ error: 'Authentication required' });
    }

    const userRoles = Array.isArray(req.user.roles)
      ? req.user.roles
      : [req.user.role].filter(Boolean);

    const permitted = userRoles.some((r) => allowedRoles.includes(r));
    if (!permitted) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }

    next();
  };
}

module.exports = { authenticate, requireRole };
3 files · javascript Explain with highlit

This snippet shows how role-based access control (RBAC) is layered onto an Express API using two small, composable middlewares and a protected router. The core idea is separation of concerns: authentication answers "who is this request?" while authorization answers "is this user allowed to do this?". Keeping those in distinct middlewares means the same identity resolution runs everywhere, and each route independently declares the roles it demands.

In auth.js, authenticate extracts a bearer token from the Authorization header, verifies it with jwt.verify, and attaches the decoded claims to req.user. Any failure — a missing header, a malformed token, an expired signature — short-circuits with a 401, so downstream handlers never run against an unauthenticated request. Because it always calls next(err) or next(), it fits Express's linear middleware chain cleanly.

The heart of the pattern is requireRole, a middleware factory: it is a function that returns a middleware, closing over the allowedRoles passed at mount time. This is why the caller can write requireRole('admin', 'editor') and get back a purpose-built guard. Using a factory avoids repeating boilerplate per route and keeps the role policy declarative and visible at the routing layer. The returned function normalizes req.user.roles to an array, checks for any intersection with allowedRoles, and returns 403 when the user is authenticated but lacks permission — an important distinction from the 401 that authenticate raises.

In admin.routes.js, the two guards are composed in order: authenticate runs first to populate req.user, then requireRole(...) enforces policy. Mounting authenticate once with router.use means every route in the file is protected by default, which is safer than opt-in protection that is easy to forget.

Finally, app.js wires the router under /admin and adds a trailing error handler that maps thrown errors to JSON responses. The trade-off of JWT-based roles is that claims are only as fresh as the token; a revoked role persists until the token expires, so short lifetimes or a revocation check are worth considering for sensitive systems.


Related snips

Share this code

Here's the card — post it anywhere.

Role-Based Access Control in Express with a requireRole Middleware Factory — share card
Link copied