Multi-tenancy segura en Lino

Multi-tenancy permite que una sola aplicación atienda a varios clientes, empresas u organizaciones manteniendo datos y permisos aislados. En Lino, Tenant se trata como parte del flujo de la aplicación: el recurso genera la base de tenancy, resuelve el Tenant por subdominio, host o slug mediante ITenantContext y expone el usuario autenticado por el JWT mediante IUserContext.

El JWT también lleva el TenantId para el cual el usuario fue autenticado. Lino compara ese Tenant con el Tenant resuelto para la solicitud, por eso esta página se centra en lo que debes decidir después de que esa base existe: modelado de entidades, permisos por organización y uso correcto de las abstracciones generadas.

Agregando soporte para Tenant

Usa el recurso de Tenant cuando el sistema sea SaaS o cuando datos de clientes distintos deban convivir en la misma aplicación sin filtrarse entre contextos.

lino feature tenant add

El asistente genera la base para el registro de Tenants, asociación de usuarios, resolución del Tenant actual, propagación por el contexto de la aplicación e integración con autenticación. El ITenantContext representa el Tenant resuelto por subdominio, host o slug, mientras que el IUserContext representa el usuario autenticado por el JWT, incluido el TenantId para el cual fue autenticado.

Después de la generación, revisa qué entidades realmente pertenecen a un Tenant y cuáles son globales. Esta revisión forma parte del modelado del producto: multi-tenancy no es solo crear una tabla de Tenants, sino decidir qué reglas, pantallas, permisos, datos y operaciones dependen del Tenant actual validado por Lino.

  • Tenant-scoped: pedidos, productos de un cliente, configuraciones de empresa, clientes de un CRM o cualquier dato que pertenezca a una organización específica.
  • Global: catálogo público de la plataforma, planes, recursos internos de operación, configuraciones administrativas o datos usados antes de elegir un Tenant.
  • Híbrido: datos globales con personalización por Tenant, que exigen modelado explícito para evitar duplicación incorrecta o filtración de reglas.

En un monolito modular, normalmente el módulo de tenancy es dueño de la entidad original de Tenant. Otros módulos, como catálogo o CRM, no deben acceder a esa entidad directamente. Cuando necesitan conocer Tenant, pueden mantener shadow entities con los campos mínimos necesarios, alimentadas por eventos de integración como creación o actualización de Tenant.

Tenant context

Con el recurso de Tenant, Lino centraliza la resolución del Tenant actual y expone ese valor mediante ITenantContext. Este contexto representa el Tenant resuelto a partir del subdominio, host o slug de la solicitud, incluida información como TenantId y slug cuando el Tenant fue encontrado.

En flujos autenticados, IUserContext lee el usuario desde el JWT. Ese JWT también contiene el TenantId para el cual el usuario fue autenticado. Lino compara el Tenant de IUserContext con el Tenant de ITenantContext, evitando que la regla de negocio confíe en contextos divergentes.

En las funcionalidades generadas o agregadas después, prefiere siempre estas abstracciones en lugar de releer claims manualmente o recibir un TenantId arbitrario en cada handler. Esto mantiene commands, queries, páginas e integraciones usando la misma fuente de verdad y preserva los mecanismos de seguridad ya implementados por Lino.

Background Jobs, consumidores de eventos e integraciones también necesitan contexto. Un job que procesa productos, clientes o usuarios tenant-scoped debe recibir explícitamente qué Tenant está siendo procesado, porque no existe una solicitud HTTP activa desde la cual ITenantContext e IUserContext puedan inferirse automáticamente.

Entidades tenant-scoped

Las entidades tenant-scoped llevan el identificador del Tenant y participan en los filtros de consulta. Con eso, el flujo común no necesita recordar manualmente aplicar TenantId en todas las queries; la funcionalidad debe usar el contexto validado y los filtros generados por Lino.

  • Incluye Tenant en aggregates que pertenecen a una organización específica.
  • Configura índices considerando Tenant y campos consultados con frecuencia.
  • Usa ITenantContext y IUserContext en commands y queries en lugar de recibir Tenant como autoridad aislada.
  • En imports y jobs, define explícitamente qué Tenant está siendo procesado.

Una entidad tenant-scoped pertenece a un Tenant. Si una entidad parece pertenecer a varios Tenants, trata eso como una decisión de modelado: quizá sea global, quizá deba existir una copia por Tenant, o quizá falte una entidad intermedia que represente la relación. Un producto, cliente o usuario también puede tener significado diferente en módulos distintos; en esos casos, una shadow entity con pocos campos puede ser más correcta que compartir la entidad original.

También diferencia referencia de ownership. Un producto en el catálogo puede referenciar el Tenant, pero eso no significa que el catálogo controle el ciclo de vida del Tenant. El módulo de tenancy sigue siendo dueño de la creación, activación, dominio, branding y modo de aislamiento; el catálogo mantiene solo los datos necesarios para filtrar y validar su propio flujo.

  • Índices: combina TenantId con campos usados en búsqueda, ordenación y unicidad por Tenant.
  • Commands: aplica la operación en el Tenant actual validado cuando la entidad sea tenant-scoped.
  • Queries: mantén paginación, filtros y contadores en el mismo contexto validado por Lino.
  • Eventos: incluye contexto suficiente para que los consumidores actualicen proyecciones sin consultar indebidamente al productor.

Aislamiento de datos

El aislamiento puede hacerse por columna, schema o base de datos separada. La elección depende de costo, volumen, requisitos contractuales y capacidad operativa.

EstrategiaVentajasCuándo usar
Columna TenantIdMás simple y barataBuena elección inicial para muchos SaaS, combinada con filtros y contexto de Lino.
Schema por TenantAislamiento intermedioÚtil cuando hay mayores exigencias de separación sin llegar a base de datos por Tenant.
Base de datos por TenantAislamiento fuerteIndicado cuando contrato, volumen u operación justifican mayor costo y automatización.

Para la mayoría de los SaaS en etapa inicial, columna por Tenant con filtros globales y contexto centralizado es un punto de partida pragmático. Lino ayuda en esa base; tu revisión debe confirmar que los aggregates tenant-scoped llevan el Tenant correcto y que las queries nuevas sigan usando el Tenant actual validado por las abstracciones generadas.

En servicios modulares, el aislamiento de Tenant y el aislamiento de módulo son preocupaciones diferentes. El módulo Catalog puede tener una tabla de productos con TenantId; el módulo CRM puede tener clientes con TenantId; y el módulo Tenancy sigue siendo dueño del registro original de Tenants. La presencia del mismo identificador no autoriza acceso directo entre módulos.

Roles y permisos por Tenant

En SaaS, un usuario puede ser administrador en un Tenant y operador en otro. Lino genera la base para trabajar con el vínculo usuario-Tenant, y los permisos de las funcionalidades deben evaluarse dentro del contexto activo validado por el framework.

También existen permisos administrativos fuera de Tenant. Una pantalla de operación de la plataforma puede ser accesible solo en el contexto del sistema, mientras que páginas como productos y clientes pueden exigir Tenant activo. Esta separación deja claro cuándo la acción es global y cuándo depende del Tenant resuelto en ITenantContext y del usuario autenticado en IUserContext.

  • Modela roles por Tenant cuando los permisos varían por organización.
  • Usa el contexto actual en las verificaciones de autorización de páginas, commands y queries.
  • Mantén claims compactas, llevando en el token solo lo que debe participar en el flujo autenticado.
  • Revoca o actualiza tokens cuando cambien permisos críticos.
ContextoEjemploCómo tratar
SistemaAdministración de la plataforma, planes, Tenants y configuraciones globales.Ejecutar fuera del alcance de un Tenant específico.
TenantProductos, clientes, pedidos, usuarios y roles de ese Tenant.Usar el Tenant actual validado por Lino.
AmbosAlgunas funciones de identidad o gestión que existen tanto en el sistema como en el Tenant.Declarar en el caso de uso qué contexto se está usando.

JWT y seguridad de Tenant

Cuando el recurso de Tenant está activo, Lino usa el JWT como fuente del contexto del usuario autenticado. El IUserContext lee las claims del usuario y el TenantId del token, sin convertir el JWT en una copia de la base de datos.

  • Mantén issuer, audience, firma y expiración configurados correctamente.
  • Usa el Tenant validado por Lino en lugar de aceptar un cambio manual basado solo en header, ruta o payload de la pantalla.
  • Evita claims demasiado grandes; los tokens inflados dificultan la operación y renovación.
  • Registra logs de acceso con usuario, Tenant y operación cuando el flujo exija auditoría.

En aplicaciones con subdominios, como acme.dev.localhost y globex.dev.localhost, el ITenantContext representa el Tenant resuelto para la solicitud. El IUserContext representa el usuario autenticado y el Tenant presente en el JWT. Lino compara estos dos contextos para evitar que un token emitido para un Tenant se use en otro contexto de Tenant.

La autorización también sigue siendo contextual. El permiso para visualizar productos, por ejemplo, vale dentro del Tenant donde fue concedido y para el cual el usuario fue autenticado. Al crear nuevas funcionalidades, usa los permisos y contextos ya disponibles en lugar de crear atajos que reciban un TenantId suelto e ignoren el flujo autenticado.

Se ha producido un error no controlado. Recargar 🗙