Saltar al contenido
Todos los artículos

Aislamiento SaaS multi-tenant con Postgres RLS y autenticación de workspace

Combina guardas de membresía de workspace en vivo en el servidor con políticas indexadas de Postgres Row-Level Security para mantener aislados los datos de cada cliente en web, móvil y trabajos en segundo plano.

el equipo de supastack2 min de lectura

Should I use Postgres Row-Level Security or application-level workspace checks?

It depends on how your application connects to Postgres. If browser or mobile clients query the database directly through PostgREST, Row-Level Security (RLS) is mandatory. If all traffic flows through a server-side TypeScript boundary, explicit membership checks on every query are your primary defense, and RLS serves as defense-in-depth against forgotten WHERE clauses. Combine live session-to-membership resolution with indexed workspace_id policies. supastack enforces server-side workspace guards across web, mobile, and desktop, and provides a free RLS policy generator for Supabase tables.

Las guardas de aplicación y las políticas de base de datos resuelven fallos distintos

A menudo se trata la autorización a nivel de aplicación y Postgres Row-Level Security como opciones excluyentes. En la práctica, detectan errores distintos: las guardas del servidor aplican jerarquía de roles, comprobaciones de derechos y registros de auditoría antes de ejecutar la lógica de negocio, mientras que RLS garantiza que olvidar una cláusula WHERE workspace_id = ... nunca exponga las filas de todos los clientes.

Tanto si usas Supabase como Postgres autoalojado con Drizzle, cada tabla perteneciente a un cliente debe incluir una clave foránea workspace_id no nula. El artículo sobre límites de workspace explica por qué ese alcance debe derivarse siempre en el servidor.

  • Las guardas de aplicación validan la vigencia de la sesión, los roles (owner, admin, member) y los derechos de funciones.
  • Las políticas RLS de la base de datos actúan como un suelo de seguridad firme ante consultas directas de cliente o ayudantes SQL en bruto.
  • Ambas capas dependen de una única fuente de verdad: la tabla de unión workspace_members.

Resuelve la membresía en vivo en lugar de confiar en cookies obsoletas

Guardar un currentWorkspaceId en una cookie del navegador o cabecera de cliente resulta cómodo para navegar, pero una cookie es una preferencia, no un permiso. Si un administrador elimina a un compañero de un workspace, el navegador de ese usuario conserva la cookie antigua.

Cruza la sesión activa con workspace_members en cada petición. Cuando la fila de membresía ya no exista, rechaza la solicitud de inmediato en lugar de esperar a que caduque un JWT o una cookie de sesión. El artículo sobre autenticación más allá del formulario de acceso detalla cómo fluye la identidad hacia esta comprobación.

export async function requireWorkspaceViewer(requestedWorkspaceId?: string) {
  const session = await requireAuthenticatedSession();
  const membership = await db.query.workspaceMembers.findFirst({
    where: and(
      eq(workspaceMembers.userId, session.user.id),
      requestedWorkspaceId
        ? eq(workspaceMembers.workspaceId, requestedWorkspaceId)
        : undefined,
    ),
    with: { workspace: true },
  });

  if (!membership) throw new ForbiddenError("Not a member of this workspace");
  return { user: session.user, workspace: membership.workspace, role: membership.role };
}

Escribe políticas de Row-Level Security rápidas e indexadas

Las políticas RLS ingenuas que llaman a auth.uid() directamente en cada fila obligan a Postgres a reevaluar la función por cada tupla. Envolver (select auth.uid()) en una subconsulta escalar permite al planificador almacenar el valor en caché como un initPlan, y un índice compuesto sobre (workspace_id, user_id) mantiene las búsquedas de membresía por debajo del milisegundo.

Puedes generar políticas SELECT, INSERT, UPDATE y DELETE listas para ejecutar en tablas multi-tenant con el generador gratuito de políticas RLS para Supabase sin escribir SQL repetitivo a mano.

CREATE INDEX IF NOT EXISTS idx_workspace_members_lookup
  ON workspace_members (workspace_id, user_id);

ALTER TABLE documents ENABLE ROW LEVEL SECURITY;

CREATE POLICY "workspace_members_manage_documents"
ON documents FOR ALL TO authenticated
USING (
  workspace_id IN (
    SELECT wm.workspace_id FROM workspace_members wm
    WHERE wm.user_id = (SELECT auth.uid())
  )
)
WITH CHECK (
  workspace_id IN (
    SELECT wm.workspace_id FROM workspace_members wm
    WHERE wm.user_id = (SELECT auth.uid())
      AND wm.role IN ('owner', 'admin', 'member')
  )
);

Preguntas frecuentes sobre RLS y autenticación de workspace

Los equipos que configuran Postgres multi-tenant suelen encontrarse con los mismos casos límite en torno a roles de servicio, trabajos en segundo plano y rendimiento.

  • ¿Drizzle ORM o Prisma se saltan RLS? — Sí, si tu servidor se conecta con el superusuario postgres o la clave service_role de Supabase. Por eso las server actions y rutas API deben llamar a requireWorkspaceViewer() y acotar cada consulta por workspaceId.
  • ¿Por qué envolver (select auth.uid()) entre paréntesis dentro de las políticas RLS? — Indica al optimizador de Postgres que evalúe auth.uid() una sola vez por sentencia en lugar de una vez por fila escaneada, evitando escaneos secuenciales lentos en tablas grandes.
  • ¿Cómo deben acceder los trabajos en segundo plano a los datos del cliente? — Pasa el workspaceId verificado en la carga útil del trabajo desde la ruta autenticada del servidor y limita las lecturas y escrituras del worker a ese workspaceId.
  • ¿Cómo verifico que el aislamiento entre clientes realmente se cumple? — Escribe pruebas negativas automatizadas en las que el Usuario A solicite o modifique el ID de registro del Usuario B en rutas web, móviles y API, y comprueba que devuelvan 403 / 404 con cero filas modificadas.

Preguntas frecuentes

14 preguntas

¿Qué es supastack?

supastack es una base modular para construir productos SaaS con IA. Reúne la infraestructura común para que puedas centrarte en las funciones propias de tu producto.

¿Soporta web y móvil?

Sí. supastack incluye apps Next.js y Expo como puntos de partida para experiencias web y móviles.

¿Qué autenticación incluye?

Better Auth cubre correo/contraseña, OTP e inicio social. Los espacios de trabajo personales se aprovisionan con la cuenta para que los registros permanezcan delimitados desde el primer día.

¿Cómo se soporta la IA?

supastack incluye IA multi-proveedor configurable, seguimiento de uso, presupuestos y soporte cifrado de claves propias (BYOK).

¿Cómo se gestionan las suscripciones?

Para productos construidos sobre el kit, la facturación Stripe, las suscripciones y los permisos forman la base del acceso de pago. Los secretos de proveedor permanecen en el servidor.

¿Dónde vive el código propio de mi producto?

Las funciones propias del producto permanecen en un codebase separado, mientras el producto configura e integra los módulos base de supastack.

¿Cómo funciona el precio de supastack?

El kit es una compra única con actualizaciones de por vida: Stack único a 249 $ —a elegir entre los repositorios Next.js (web), Expo (iOS y Android) o Tauri (macOS y Windows)—, Doble stack a 398 $ con dos repositorios a elegir, o Paquete completo a 399 $ con los tres repositorios y el monorepo completo, más 75 $ por plaza de desarrollador adicional. Sin suscripción al kit. El pago en este sitio entrega tu clave de licencia por correo, con acceso al repositorio por invitación de GitHub.

¿Cómo delimitan los espacios de trabajo mis datos?

Los registros del usuario se delimitan por workspaceId. Identidad, permisos, uso de IA, eventos de facturación y entradas de auditoría permanecen dentro del límite del espacio personal.

¿Cómo funcionan los jobs en segundo plano?

Trigger.dev es el runtime de jobs explícito. El contrato JobRunner solo promete enqueue, status y cancel — la semántica de workflow sigue siendo específica y explícita de Trigger.dev.

¿En qué se diferencia el email de la divulgación?

El email transaccional cubre flujos de producto como avisos de finalización de tareas. La divulgación en frío y las campañas pertenecen al producto, fuera del módulo de email de este kit.

¿Cómo funcionan las compras móviles?

La app móvil incluye compras nativas y restauración con RevenueCat, con acceso verificado por tu servidor. Configura las cuentas de las tiendas y completa las pruebas en dispositivos antes de publicar. La facturación web es independiente.

¿Dónde residen las claves de los proveedores de IA?

Las credenciales BYOK están cifradas y permanecen en el servidor. Las claves se saneen en los logs; el uso y los presupuestos siguen visibles a nivel de espacio de trabajo.

¿Qué métodos de pago acepta el checkout?

El checkout alojado en Stripe acepta tarjetas y, una vez activadas las billeteras en la cuenta de Stripe, también puede ofrecer Alipay y WeChat Pay — muy útil para compradores en China. Las autorizaciones de billetera se confirman de forma asíncrona, por lo que la página de la licencia se actualiza en cuanto se liquida el pago.

¿Puedo obtener un reembolso?

Una vez que tienes acceso al repositorio, supastack es tuyo para siempre, por lo que no se puede reembolsar. Pero no te preocupes: obtienes el código fuente completo con actualizaciones de por vida para crear y lanzar productos ilimitados para ti o tus clientes. Solo no puedes revender el código ni redistribuir el repositorio como plantilla o starter kit. Una excepción es el cashback de lanzamiento — lanza un producto real creado con supastack, compártelo y alcanza 5.000 visualizaciones orgánicas o 50 Me gusta orgánicos en un plazo de 60 días para recuperar el 50 % de lo que pagaste; consulta los términos.

ES