Diseño de RBAC Multiempresa para un SaaS
La autorización multiempresa se vuelve mucho más clara cuando identidad, membresía, rol y tenant activo se tratan como conceptos separados. El problema normalmente no está en los nombres de los roles, sino en concentrar demasiadas decisiones de autorización dentro del registro de usuario.
Separar identidad de membresía
Un usuario responde quién es una persona. Una membresía responde a qué empresa pertenece y qué puede hacer allí. Mantener esos conceptos separados permite que una misma identidad participe en varias empresas sin duplicar usuarios ni sobrecargar un único user.role.
En la práctica, un usuario puede tener una identidad y varias membresías. Cada membresía pertenece a un tenant y contiene el rol y las preferencias específicas de esa empresa.
User
id
email
TenantMembership
userId
tenantId
role
notificationsEnabled
UNIQUE(userId, tenantId)Los roles pertenecen al contexto de la empresa
Un rol como OWNER, ADMIN, AGRONOMO, OPERADOR o VIEWER sólo tiene sentido dentro de una empresa. La misma persona puede ser propietaria en un tenant y visualizadora en otro.
Por eso los roles de empresa deben vivir en la membresía y no en el usuario. Puede existir un rol global para funciones de plataforma como SUPERADMIN, pero no debería usarse como atajo para la autorización normal de cada tenant.
Resolver el tenant activo de forma explícita
Cada request asociado a una empresa necesita un tenant activo. El backend debe resolverlo desde un contexto de sesión confiable y verificar que el usuario todavía tenga una membresía válida para ese tenant.
Un orden de fallback útil es: tenant solicitado si la membresía es válida, luego la empresa por defecto si sigue siendo válida y finalmente otra membresía vigente. Si no existe ninguna, la sesión debe quedar en estado sin tenant u onboarding en lugar de reutilizar silenciosamente contexto viejo.
tenant solicitado
↓ ¿membresía válida?
tenant por defecto
↓ ¿membresía válida?
primera membresía válida
↓
NO_TENANT / onboardingGuardar el contexto de tenant en la sesión
Una vez resuelto el tenant activo, el servidor puede emitir una sesión o token con tenantId y memberRole junto con cualquier rol global de plataforma.
El cliente puede pedir un cambio de empresa, pero no debería ser confiable para declarar libremente el tenant en cada llamada. La autorización sigue derivando de la membresía validada por el backend y de la sesión generada a partir de ella.
Usar capacidades en lugar de repartir checks de roles
Condiciones directas como if role === ADMIN se multiplican rápido por los controladores y vuelven difícil entender la política completa. Una matriz de capacidades permite definir en un único lugar qué puede hacer cada rol.
Los controladores pueden proteger operaciones como fields.write, team.invite, billing.manage o analytics.view sin duplicar la semántica de roles. También permite expresar excepciones: por ejemplo, un ADMIN puede gestionar campos y equipo pero no facturación, mientras que un OWNER puede hacer ambas cosas.
OWNER -> fields.*, team.*, billing.manage
ADMIN -> fields.*, team.*, billing.view
AGRONOMO -> fields.read, alerts.*, analytics.*
OPERADOR -> fields.read, notes.write
VIEWER -> *.readValidar membresía y capacidad
La autorización necesita dos comprobaciones: si el usuario pertenece al tenant y si el rol de esa membresía permite la acción solicitada. Validar sólo la capacidad puede autorizar la acción correcta dentro de la empresa equivocada.
Prefiero guardias centralizados para membresía y evaluación de rol o capacidades. Así los controladores se concentran en el comportamiento de la aplicación mientras el límite de autorización permanece consistente en campos, alertas, analítica, notificaciones, equipo y facturación.
Cambiar de empresa también es un problema de estado del frontend
El cambio de tenant no termina cuando el backend devuelve un nuevo tenantId. El cliente todavía puede tener queries cacheadas, filtros seleccionados, formularios abiertos o requests en vuelo de la empresa anterior.
Al cambiar de empresa hay que cancelar requests tenant-scoped, invalidar caches o incluir tenantId en sus keys y resetear el estado específico de empresa. De lo contrario, un backend correcto puede convivir con un frontend que durante un momento muestre datos viejos de otro tenant.
queryKey: ["fields", tenantId]
queryKey: ["alerts", tenantId]
queryKey: ["analytics", tenantId]Las invitaciones deben crear membresía, no otra identidad
Invitar a alguien a una empresa debería converger al mismo modelo de membresía tanto si el email ya pertenece a un usuario como si esa persona todavía necesita registrarse.
Para un usuario existente y verificado, aceptar la invitación puede crear directamente la membresía. Para uno nuevo, primero ocurren registro y verificación, y luego la invitación crea la membresía. La invitación es temporal; la membresía es el registro durable de autorización.
Los cambios de rol y bajas deben aplicarse inmediatamente
La membresía es dinámica. Un usuario puede ser eliminado de una empresa o perder permisos mientras todavía conserva una sesión vieja o una pestaña abierta.
Los requests sensibles deberían validar la membresía actual en lugar de confiar en una vista antigua del rol. El frontend también debe refrescar el contexto de empresas y usuario después de modificar el equipo para que navegación y permisos no queden desactualizados.
El onboarding sin tenant es un estado válido
Un SaaS puede tener un usuario autenticado que todavía no pertenece a ninguna empresa. Tratarlo como un error de autenticación vuelve incómodo el onboarding y genera excepciones por todos lados.
Un diseño más limpio mantiene al usuario autenticado pero sin tenant activo. Sólo se habilitan onboarding y creación de empresa hasta que exista un tenant; entonces la nueva membresía pasa a ser el contexto activo.
El límite que hay que proteger
La regla más importante de un RBAC multiempresa es que toda lectura y escritura tenant-scoped derive su tenant de un contexto de autorización confiable. Filtrar por tenantId no es sólo un detalle de consulta; forma parte del límite de seguridad.
Un buen modelo hace que el camino correcto sea más fácil que el peligroso: membresías explícitas, roles contextuales, capacidades centralizadas, sesiones con tenant resuelto y caches conscientes del tenant. El resultado es más simple de extender y mucho más difícil de usar mal por accidente.
Trabajo relacionado
Ver el modelo de autorización multiempresa en el caso de estudio de Vienor