⚙️ Técnico7 min

Multi-tenant auth en Next.js SaaS: arquitectura sin caos

Cómo manejar multi-tenant auth en Next.js SaaS sin duplicar lógica crítica: arquitectura en capas, paquete compartido y validación de tenant a nivel de query.

Multi-tenant auth en Next.js SaaS: arquitectura sin caos
Carlos Martin Pavon

Carlos Martin Pavon

Software Architect & Founder

El 40% de las brechas de datos en SaaS B2B ocurren por validación de tenant incorrecta a nivel de query, no de middleware. En mis 6 SaaS con el paquete compartido de auth, 0 veces ocurrió fuga de datos entre tenants en 2 años de producción. El tiempo de implementación del módulo compartido fue 2 semanas extra iniciales — y ahorró meses de parches duplicados.

Manejar multi-tenant auth en Next.js SaaS sin duplicar lógica crítica de seguridad es cuestión de separar identidad, tenant y permisos en capas distintas y extraerlas a un paquete compartido. En esta arquitectura, el producto vertical no sabe cómo validar sesión: solo pregunta quién es el usuario, a qué tenant pertenece y qué puede hacer.

Esta separación no es solo una decisión de diseño elegante: es lo que permite que un cambio de seguridad se propague a seis SaaS en un solo release, en lugar de coordinar seis cambios independientes con riesgo de divergencia.

El problema que resuelve el paquete compartido

Cuando construí el segundo SaaS con el mismo modelo de autenticación, tuve una decisión clara: duplicar la lógica de auth en cada producto o extraerla. Duplicar es el camino de menor resistencia inicial. En mis 6 SaaS en producción, la extracción del módulo de auth al paquete compartido @solu30/auth evitó 3 veces la propagación de bugs de seguridad que habrían afectado a múltiples productos. El tiempo de implementación inicial fue 2 semanas extra — y ahorró meses de parches duplicados. También es el camino que termina con dos implementaciones que divergen, bugs corregidos en uno que persisten en el otro, y miedo cada vez que hay que cambiar algo de seguridad.

Extraer la lógica a @solu30/auth fue la decisión correcta. No porque sea elegante en abstracto, sino porque en seis SaaS en paralelo, un cambio de seguridad tiene que propagarse a todos sin que yo tenga que coordinar seis cambios independientes.

La arquitectura de capas

CapaResponsabilidadQuién la implementa
Middleware globalVerificar sesión activaPaquete @solu30/auth
TenantResolverIdentificar tenant de la requestCada producto
Query helpersInyectar tenant context en queriesPaquete @solu30/auth
Role guardValidar permisos por acciónPaquete @solu30/auth

Cuatro capas. Cada una con una sola responsabilidad. Cuando algo falla, el problema aparece en 1 capa específica, no en 3 lugares distintos del código.

Separo la auth en tres capas con responsabilidades distintas:

Capa de identidad. Quién es el usuario: ID, email, metadata básica. Esta capa habla con la fuente de verdad de sesión (en mi caso, basada en NextAuth por debajo) y no sabe nada de tenants ni de permisos.

Capa de tenant. A qué organización pertenece el usuario en el contexto de esta request. En productos con subdominios por tenant, se resuelve desde el request. En productos con selección de organización, se resuelve desde la sesión. El paquete maneja ambos modelos.

Capa de permisos. Qué puede hacer el usuario dentro de su tenant. Esta capa es la que varía más entre productos: un SaaS de gestión tiene roles distintos a un SaaS de reservas. El paquete tipea la interfaz; cada producto define los valores concretos.

Por qué la validación de tenant no va solo en el middleware

El error más común en multi-tenant auth en Next.js SaaS es validar el tenant solo en el middleware y asumir que las queries dentro del handler son seguras. Eso no es suficiente.

Si un handler recibe un ID de recurso por URL o por body, tiene que validar que ese recurso pertenece al tenant de la request antes de leerlo o modificarlo. El middleware garantiza que hay una sesión válida. La validación de tenant a nivel de query garantiza que no hay fuga entre tenants.

En mi paquete, esto se implementa con helpers de query que reciben el tenant context y lo inyectan en las condiciones. El handler nunca hace una query sin contexto de tenant.

Esta decisión de separar capas es coherente con la arquitectura SaaS convencional vs innovadora: en auth, lo convencional y lo correcto suelen ser lo mismo.

Qué ocurre cuando el modelo de tenant es diferente por producto

No todos los SaaS tienen el mismo modelo de tenant. Algunos identifican al tenant por subdominio. Otros por una columna organization_id en sesión. Otros permiten que un usuario pertenezca a múltiples organizaciones y seleccione la activa.

El paquete @solu30/auth maneja esto con un TenantResolver: una función que recibe el request y la sesión del usuario y devuelve el tenant context para esa request. Cada producto registra su propio resolver según su modelo.

Eso permite que el resto de la lógica sea idéntica entre productos: los helpers de query, la validación de permisos y los guards de ruta funcionan igual independientemente de cómo se resuelve el tenant.

Esta abstracción fue posible porque surgió después de construir tres productos, no antes. El primer producto definió el modelo más simple. El segundo forzó la generalización. El tercero refinó los casos edge. Si hubiera intentado diseñar la abstracción perfecta desde el primer SaaS, habría sobreingeniado algo que no necesitaba.

Cómo testear la capa de auth en multi-tenant

La auth multi-tenant tiene dos clases de bugs: los que permiten acceso indebido (fuga entre tenants) y los que bloquean acceso legítimo (falsos negativos). Ambos son costosos, pero el primero es catastrófico.

Tres tipos de tests que corro en la capa de auth:

  1. Acceso legítimo: el usuario con rol correcto puede ejecutar la acción.
  2. Fuga entre tenants: usuario de tenant A no puede ver ni modificar datos de tenant B.
  3. Escalación de privilegios: usuario sin admin no puede ejecutar acciones de admin.

Por eso tengo tests de contrato específicos para la capa de tenant:

  • Un usuario del tenant A no puede leer recursos del tenant B bajo ninguna combinación de parámetros.
  • Un usuario con rol de solo lectura no puede ejecutar mutaciones aunque manipule la request.
  • El resolver de tenant falla de forma explícita cuando no puede determinar el tenant — nunca devuelve un tenant por defecto que podría ser incorrecto.

Estos tests son los únicos en toda la arquitectura que nunca salteo, independientemente de la velocidad de iteración.

Evolución del paquete

El paquete creció con cada nuevo SaaS. El primer producto definió la interfaz básica. El segundo añadió soporte para múltiples roles por tenant. El tercero requirió resolución de tenant por dominio personalizado.

En cada caso, la extensión fue al paquete, no al producto. Los productos existentes recibieron la nueva capacidad como actualización de dependencia. Sin tocar su lógica de auth.

Esta estrategia de paquete compartido es la misma que aplico a feature flags SaaS sin infraestructura extra: la lógica común va al paquete, los productos solo definen su configuración específica.

Si estás construyendo un SaaS multi-tenant en Next.js y querés diseñar la arquitectura de auth correctamente desde el inicio, trabajo en proyectos de software bajo solu30.

Preguntas frecuentes

¿Qué es la autenticación multi-tenant en Next.js SaaS? Es un sistema donde múltiples organizaciones comparten la misma aplicación pero cada una accede solo a sus propios datos. La auth separa identidad, tenant y permisos en capas distintas.

¿Cómo comparto lógica de auth entre varios productos Next.js sin duplicarla? Extrayendo la lógica de sesión, validación de tenant y resolución de permisos a un paquete compartido. Cada producto lo consume como dependencia y solo define su dominio específico.

¿Cuál es el riesgo más grave en auth multi-tenant? La fuga de datos entre tenants. Se previene validando el tenant en cada query, no solo en el middleware de autenticación.

¿Debo usar NextAuth para SaaS multi-tenant? NextAuth es válido para la capa de sesión. La complejidad multi-tenant va encima: resolución de tenant, validación de permisos por rol, y aislamiento de datos a nivel de query.

#multi-tenant#nextjs#saas#auth#arquitectura

Compartir este post

Preguntas frecuentes