Cuando admin/editor/usuario deja de servirte
Casi todas las aplicaciones arrancan con tres roles: administrador, editor, usuario. Para un MVP, vale. Para un producto con cuatro pantallas, también. El problema aparece cuando el cliente te pide que un perfil del departamento de finanzas vea los informes de facturación, pero no toque el CRM. Cuando un colaborador externo solo debe acceder a dos proyectos durante seis semanas. Cuando un responsable regional gestiona su zona y nada más.
Ahí, el esquema clásico revienta. Empiezas a crear roles tipo editor-finanzas-norte-solo-lectura y, en seis meses, tienes cuarenta combinaciones que nadie entiende ni audita.
La salida es un sistema de permisos granulares con roles dinámicos. ¿Te preguntas cómo implementar un sistema de permisos granulares y control de acceso basado en roles dinámicos en tu aplicación web a medida sin terminar enterrado en deuda técnica? Vamos por partes, con criterio técnico y sin atajos peligrosos.
RBAC, ABAC y por qué el híbrido suele ganar
Antes de tocar código, decide el modelo. Cada uno tiene su lógica y sus costes.
RBAC (Role-Based Access Control)
El más extendido. El usuario tiene uno o varios roles, cada rol agrupa permisos, los permisos definen qué se puede hacer y sobre qué.
- A favor: sencillo de razonar y de administrar mientras el catálogo de roles no se dispare.
- En contra: la famosa explosión de roles. En cuanto necesitas combinar condiciones específicas, multiplicas roles hasta que nadie sabe qué hace cada uno.
ABAC (Attribute-Based Access Control)
Aquí las decisiones se toman evaluando atributos del usuario, del recurso y del contexto. Algo del tipo: "un usuario con departamento=ventas puede editar recursos con tipo=propuesta siempre que la petición ocurra dentro del horario laboral".
- A favor: flexibilidad real. Políticas complejas sin inventarse roles nuevos.
- En contra: más difícil de auditar y de explicárselo a alguien que no sea de tu equipo técnico.
El híbrido: RBAC dinámico con capa ABAC
Para la mayoría de aplicaciones web a medida, el mejor compromiso es combinar ambos. RBAC marca el esqueleto, ABAC refina los casos donde un rol simple se queda corto. Roles como base, políticas como afinador. Es el modelo que desarrollamos a partir de aquí.
En la práctica, esto significa que el 90% de los accesos cotidianos se resuelven con una comprobación de rol rápida, y solo el 10% restante (los que dependen de hora, ubicación, estado del recurso o relación contractual) atraviesa el motor de políticas. La factura en rendimiento, mínima. La factura en mantenibilidad, mucho menor que un ABAC puro.
Cómo se estructura un sistema de permisos granulares
Las entidades que necesitas
Un diseño limpio se apoya en seis piezas:
- Usuario: la persona o servicio que pide acceso.
- Rol: un conjunto nombrado de permisos. Gestor de proyectos, analista financiero, soporte de nivel 2.
- Permiso: una acción concreta sobre un tipo de recurso, expresada normalmente como
recurso:acción. Ejemplos:proyecto:crear,factura:ver,usuario:eliminar. - Recurso: el objeto sobre el que se aplica el permiso. Puede ser una entidad concreta (el proyecto 42) o un tipo (cualquier proyecto).
- Política: una regla que evalúa atributos y contexto para decidir. Funciona como capa adicional sobre los permisos de rol.
- Scope o ámbito: delimita el alcance. Un rol puede tener
proyecto:editar, pero solo dentro de su equipo o de su región.
El esquema en base de datos
Lo mínimo que vas a necesitar:
roles: id, nombre, descripción,is_system(para diferenciar los roles del sistema de los personalizados).permissions: id, resource, action, descripción.role_permissions: role_id, permission_id (muchos a muchos).user_roles: user_id, role_id,scope_type,scope_id,expires_at.policies: id, nombre, condiciones (en JSON o en un DSL propio), efecto (allow/deny), prioridad.
Las columnas scope_type y scope_id en user_roles son la pieza que hace dinámico todo el sistema. Un usuario puede tener el rol "editor" con scope_type=proyecto y scope_id=42, y solo será editor de ese proyecto. Otro puede tener el mismo rol sin scope y editar todos.
expires_at te permite asignaciones temporales: colaboradores externos con contrato cerrado, accesos de emergencia, periodos de prueba. Caduca solo. Sin tickets manuales para revocar después.
Implementación, paso a paso
1. El motor de evaluación
Es el corazón. Decide si un usuario puede ejecutar una acción sobre un recurso. El flujo:
- Recopila los roles del usuario, incluidos los heredados si manejas jerarquía.
- Junta los permisos de esos roles, filtrados por scope aplicable.
- Evalúa las políticas ABAC que apliquen al contexto.
- Resuelve: si una política deniega explícitamente, denegar. Si algún permiso concede y ninguna política deniega, conceder. En cualquier otro caso, denegar.
Deny-by-default. No negociable. Si no hay un permiso explícito que diga "sí", la respuesta es "no". Cualquier excepción a esta regla, por bienintencionada que parezca, abre un agujero que tarde o temprano alguien va a encontrar.
2. Middleware de autorización
En una aplicación web, el sitio natural para colocar el control es un middleware que intercepta la petición antes de llegar al controlador. Recibe el usuario autenticado, deduce la acción a partir del endpoint y del método HTTP, identifica el recurso desde los parámetros de la ruta y llama al motor.
¿Rendimiento? Cachea los permisos efectivos del usuario en memoria o en Redis. Invalida la caché cuando cambien sus roles, sus políticas o el propio usuario. No cachear esto te va a costar latencia en cada petición autenticada.
3. API de administración
Alguien tiene que gestionar todo esto sin abrir la base de datos. Necesitas endpoints para:
- CRUD de roles personalizados (los del sistema no se tocan).
- Asignar y revocar roles a usuarios, con soporte de scope y expiración.
- Crear y editar políticas ABAC.
- Auditoría: cada cambio en roles, permisos y asignaciones queda registrado.
4. Jerarquía de roles
Permitir que un rol herede de otro reduce duplicación. "Administrador de departamento" hereda de "editor" y suma sus propios permisos. Modélalo como relación padre-hijo en roles y resuelve los permisos de forma recursiva. Pon un límite de profundidad y detección de ciclos, o tarde o temprano alguien construirá un grafo circular que te tumbe la app.
5. Permisos a nivel de recurso individual
A veces el scope por tipo no llega. Quieres que María tenga permisos sobre el documento 1.834 concretamente, al margen de su rol general. Una tabla resource_permissions con user_id, resource_type, resource_id, permission_id cubre el caso. Evalúala después de los permisos de rol, para que funcione como refinamiento, no como sustituto.
Seguridad: lo que no puedes saltarte
- Mínimo privilegio. Cada usuario, solo los permisos que necesita para hacer su trabajo. "Por si acaso" no es un criterio.
- Separación de funciones. Acciones críticas (borrado de datos financieros, transferencias, cambios en producción) deberían exigir aprobación de un segundo perfil.
- Auditoría. Cada evaluación, cada cambio de rol, cada acceso a un recurso sensible: timestamp, usuario, recurso, resultado. Sin excepciones.
- Revocación inmediata. Si desactivas un usuario o modificas su rol, los cambios se propagan ya. Invalida la caché. Si usas JWT, ten lista de revocación o tokens de vida corta con refresh.
- Tests, muchos tests. Los permisos son uno de los puntos donde un fallo silencioso se convierte en titular. Cubre cada combinación de rol, permiso y scope en los flujos críticos con tests automatizados que se ejecuten en CI.
Rendimiento cuando la cosa crece
Miles de usuarios y decenas de roles convierten la evaluación de permisos en un cuello de botella si no la diseñas bien. Tres estrategias que funcionan:
- Precalcula los permisos efectivos del usuario y guárdalos en caché. Invalida de forma selectiva cuando cambien los datos relevantes, no toda la caché en cada modificación.
- Usa bitfields o conjuntos indexados para resolver permisos en memoria, sin pegarle a la base de datos en cada request.
- Si el dominio se complica, externaliza la lógica a un motor especializado: Open Policy Agent (OPA), Casbin o Cerbos. Te quitan complejidad del backend y centralizan las políticas.
¿Cuándo invertir en esto?
No todas las aplicaciones necesitan este nivel desde la primera sprint. Pero hay señales claras que indican que ha llegado el momento: manejas datos sensibles, tu producto sirve a varias organizaciones desde una misma instancia, tienes perfiles con responsabilidades muy diferenciadas o entras en un marco regulatorio (RGPD, ISO 27001, ENS, SOC 2). Cuando aparece cualquiera de esos cuatro, un sistema granular se paga solo.
Lo que sí recomendamos a cualquier equipo: aunque arranques con tres roles, deja la arquitectura preparada para crecer. Modela permisos como recurso:acción desde el día uno, separa el motor de evaluación del resto del código y registra la auditoría desde la primera versión. Migrar más tarde, con producción en marcha y miles de asignaciones que rehacer, es siempre más caro que diseñarlo bien al principio.
Si quieres llevar tu aplicación web a medida a ese nivel de control de acceso, diseñamos contigo la arquitectura de permisos.