Inicio / Artículos

Control de acceso basado en roles en Express con JWT y dos middleware

Aprenda cómo aplicar el acceso basado en roles en una API de Express combinando el middleware de autenticación JWT con un guardián authorize() reutilizable, y cuándo responder con 401 en lugar de 403.

Editor: REACTAPP.TOP4 min de lectura1024 palabras

Fuentes: What is RBAC in Node.js?

En la mayoría de los backends, se permite que diferentes usuarios realicen distintas acciones: un administrador puede eliminar cuentas, un gerente puede editarlas y un usuario regular solo puede leer. Distribuir verificaciones de tipo if (user.role === ...) a través de los controladores rápidamente se vuelve difícil de mantener y propenso a errores. Esta guía muestra cómo expresar esas reglas mediante control de acceso basado en roles (RBAC) en Express con dos middleware simples, y cómo elegir el código de estado adecuado cuando se rechaza una solicitud.

Una matriz típica de permisos se ve así:

Admin   → Create, Read, Update, Delete
Manager → Read, Update
User    → Read

Cómo encaja el RBAC en la pipeline de solicitudes

El RBAC es un modelo de autorización: el acceso a un endpoint se concede o deniega en función del rol asignado al solicitante. En una API de Node.js basada en JWT, los componentes se organizan en un orden fijo:

Login
  ↓
JWT Token
  ↓
Authentication Middleware
  ↓
Check User Role
  ↓
Authorization Middleware
  ↓
Controller

Se responden dos preguntas separadas a lo largo del proceso. La autenticación determina quién es el solicitante, mientras que la autorización decide qué puede hacer ese solicitante. Al mantenerlas en middleware distintos, cada una se vuelve simple y susceptible de pruebas. La distinción se explica con más profundidad en dónde pertenecen la autenticación y la autorización.

Asociación de roles a endpoints

Considere un conjunto estándar de rutas para la gestión de usuarios:

GET    /api/users
POST   /api/users
PUT    /api/users/:id
DELETE /api/users/:id

La política deseada para ellas:

Admin   → All APIs
Manager → GET, PUT
User    → GET only

En lugar de volver a verificar el rol en cada controlador, la política puede asociarse de forma declarativa a cada ruta.

Incluir el rol en el token

Cada registro de usuario lleva un rol:

{
  name: "Ash",
  email: "ash@example.com",
  role: "admin"
}

Al iniciar sesión, el rol se incluye en el JWT junto con el ID de usuario, de modo que las solicitudes posteriores pueden autorizarse sin necesidad de consultar la base de datos:

const token = jwt.sign(
  {
    userId: user._id,
    role: user.role
  },
  process.env.JWT_SECRET,
  { expiresIn: "1h" }
);

El compromiso: el rol queda fijo en el token hasta que este vence. Si a un administrador se le reduce su nivel, el token antiguo seguirá indicando admin durante hasta una hora. Los tiempos de vencimiento cortos limitan ese período; para operaciones sensibles, es más seguro volver a leer el rol desde la base de datos.

Autenticación de la solicitud

El primer middleware extrae el token del encabezado Authorization: Bearer <token>, verifica su firma y fecha de vencimiento, y almacena la carga descompresada en req.user. Los tokens faltantes o inválidos hacen que la solicitud finalice con un código 401:

const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(" ")[1];
if (!token) {
    return res.status(401).json({
      message: "Authentication required"
    });
  }
  try {
    const decoded = jwt.verify(
      token,
      process.env.JWT_SECRET
    );
    req.user = decoded;
    next();
  } catch (error) {
    return res.status(401).json({
      message: "Invalid or expired token"
    });
  }
};

Una vez que se ejecuta, cada manejador posterior puede confiar en req.user. Al verificar, también es buena práctica especificar los algoritmos aceptados mediante la opción algorithms de jwt.verify en lugar de aceptar cualquier cosa que indique el encabezado del token.

Autenticación por rol

El segundo middleware es una fábrica. Toma los roles permitidos y devuelve un manejador que rechaza a cualquiera cuyo rol no esté en la lista, devolviendo un error 403:

const authorize = (...roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({
        message: "Access denied"
      });
    }
next();
  };
};

Tenga en cuenta que lee req.user.role, por lo que siempre debe ejecutarse después de authenticate; por sí solo generaría un error debido a que req.user estaría indefinido.

Proteger una ruta ahora consiste en combinar estos dos pasos:

router.delete(
  "/users/:id",
  authenticate,
  authorize("admin"),
  deleteUser
);

Solo un administrador autenticado puede acceder a deleteUser. Permitir varios roles simplemente implica pasar más argumentos:

router.put(
  "/users/:id",
  authenticate,
  authorize("admin", "manager"),
  updateUser
);

Aquí tanto los administradores como los gerentes pueden actualizar a los usuarios.

Elegir entre 401 y 403

Estos dos códigos se confunden con frecuencia, pero indican problemas diferentes.

401: el solicitante no está autenticado

El servidor no sabe quién está realizando la solicitud, generalmente por alguna de estas razones:

No token
Invalid token
Expired token

El cliente debe iniciar sesión nuevamente o actualizar su token.

403: el solicitante es conocido pero no tiene permiso

La identidad está verificada, pero el rol no otorga acceso:

User role = user
Required role = admin

Volver a intentarlo con las mismas credenciales no ayudará. Una regla sencilla: 401 significa “no sabemos quién eres”; 403 significa “lo sabemos, y la respuesta es no”.

Revisión rápida

  • ¿Qué es RBAC? Control de acceso en el que los permisos de la API provienen del rol asignado al solicitante.
  • ¿Cómo se implementa en Express? Un middleware verifica el JWT, y otro comprueba si el rol está incluido en la lista permitida para esa ruta.
  • ¿Dónde debe aplicarse? En el backend. Ocultar botones en la interfaz de usuario es solo estético, ya que cualquiera puede llamar directamente a la API.
  • ¿Por qué usar middleware? Permite que la autorización sea reutilizable y quede separada de la lógica de negocio, de modo que una sola protección sirva para múltiples rutas.
  • ¿Qué no cubre el RBAC? Las reglas de propiedad, como “los usuarios solo pueden editar su propio perfil”, requieren una verificación adicional contra el recurso en sí.
  • Conclusión

    El flujo completo es sencillo:

    Client
      ↓
    JWT
      ↓
    Authentication
      ↓
    Authorization / RBAC
      ↓
    Controller
      ↓
    Database
    

    Autentique al llamante, lea su rol, compárelo con lo que permite la ruta y luego autorice o rechace la solicitud. A medida que la aplicación crece, considere asociar roles a permisos más detallados como user:create, user:update y user:delete, y verificar los permisos en las rutas en lugar de usar nombres de roles. De este modo, agregar un nuevo rol significará editar solo una asociación y no todas las rutas.

    Lecturas relacionadas

  • Seguridad de APIs Express: Autenticación, Validación, Límites de Tasa y Monitoreo — Una guía práctica sobre la seguridad de APIs Express: autenticación con Passport y JWT, modelos de autorización, cifrado AES-GCM, validación, límites de tasa y registro de eventos.
  • Validación de Entrada en el Lado Servidor en Express con Cadenas de express-validator — Aprenda por qué las APIs Express deben validar la entrada en el servidor, cómo las cadenas de express-validator registran errores para validationResult y cómo estructurar una ruta para producción.