Todos los artículos
22 de octubre de 2026

Compartir autenticación y contratos de datos entre un backend Next.js y una app SwiftUI, 2026

Un backend compartido para SwiftUI y Next.js funciona con un solo proveedor de autenticación que emite los tokens y un solo esquema Postgres que genera los tipos.

Desarrolladora con auriculares escribe en un portátil junto a dos monitores de código, frente a una ventana

Un backend compartido para una app SwiftUI y una app Next.js funciona cuando dos cosas tienen una única fuente: un proveedor de autenticación que emite los tokens y un esquema de base de datos que genera los tipos para ambos clientes. Todo lo demás es instalación. Este artículo usa Supabase como ejemplo concreto, porque su documentación cubre las dos mitades, y señala dónde la idea se aplica a cualquier proveedor.

Si vas a añadir una app de iOS a un producto web que ya existe, lo difícil casi nunca es la primera petición. Son las costuras: un token que caduca a mitad de una petición, un campo que es nulo en una plataforma y obligatorio en la otra, un usuario que cierra sesión en el teléfono y sigue siendo válido en el servidor. Los traspasos entre ambas superficies, como los enlaces universales y el traspaso de la web a la app, solo funcionan si los dos lados coinciden en quién es el usuario.

Una identidad, dos clientes

Elige al proveedor de autenticación como fuente de verdad de la identidad. Ningún cliente es su dueño. La app web y la app de iOS inician sesión en el mismo proyecto, así que ambas reciben tokens para el mismo ID de usuario, y toda regla de base de datos que dependa de ese ID se aplica igual en la web y en iOS.

Los dos clientes guardan la sesión de forma distinta. En el navegador, la guía de Supabase para Next.js mantiene la sesión en una cookie llamada sb-<project_ref>-auth-token. Una app nativa no tiene un almacén de cookies en el que apoyarse, así que guarda la sesión por su cuenta y envía el token de acceso a tu servidor en una cabecera Authorization: Bearer. Esa cabecera es el patrón que recomendamos, no algo que el proveedor imponga. Lo que se comparte es el JWT, no el almacenamiento.

Esta división importa para la arquitectura. Un panel renderizado en el servidor y un cliente nativo tienen modos de fallo distintos, un punto que se trata en arquitectura de un sitio de marketing frente a un panel SaaS. Decide pronto qué peticiones van directo a la base de datos bajo seguridad a nivel de fila y cuáles pasan por un route handler de Next.js. Pasa por Next.js cuando el trabajo necesite un secreto que solo vive en el servidor, una llamada a un tercero o lógica de negocio que no debe estar en un dispositivo.

Qué verifica el servidor Next.js

El servidor verifica el token por sí mismo. Nunca acepta una identidad que el cliente se limita a afirmar.

Diagrama: mapa de nodos con tres partes: Cookies for the browser, Verify, never trust y Asymmetric keys and JWKS
Mapa de nodos: Qué verifica el servidor Next.js.

Cookies para el navegador, tokens bearer para SwiftUI

Las páginas renderizadas en el servidor leen la sesión de la cookie mediante @supabase/ssr. Los route handlers que atienden a la app de iOS leen la cabecera bearer. Los dos caminos terminan en la misma pregunta: ¿es válido este token y de quién es?

Verificar, nunca confiar

La guía de Supabase para el lado del servidor es tajante: "Never trust supabase.auth.getSession() inside server code such as Proxy" (nunca confíes en getSession() dentro de código de servidor como el Proxy). Su razonamiento es que la sesión "is loaded directly from local storage and isn't re-validated against the Auth server" (se carga directamente desde el almacenamiento local y no se revalida contra el servidor de autenticación), y que "Anyone can forge the session cookie" (cualquiera puede falsificar la cookie de sesión). La guía de autenticación del lado del servidor para Next.js recomienda getClaims para proteger páginas y datos de usuario. Con claves de firma asimétricas, getClaims verifica el token en local contra claves públicas en caché. Con un secreto simétrico, llama al servidor de autenticación. getUser siempre hace una llamada de red y devuelve el registro de usuario más reciente, a costa de latencia.

Claves asimétricas y JWKS

Si un route handler verifica el token bearer sin la librería cliente, usa una librería de JWT contra el endpoint JWKS del proyecto, https://project-id.supabase.co/auth/v1/.well-known/jwks.json. La documentación de JWT de Supabase dice que no implementes tú mismo la verificación de firmas. También recomienda claves asimétricas frente a un secreto compartido HS256: un secreto filtrado permite suplantar usuarios, y es fácil exponerlo por accidente con prefijos como NEXT_PUBLIC_. Los claims que leerás son sub (el ID de usuario), exp (caducidad) y role (el rol de Postgres que se usa para la seguridad a nivel de fila).

Vida del token y renovación

Los tokens de acceso son de vida corta. Según la documentación de sesiones de Supabase, el rango habitual va de 5 minutos a 1 hora, el valor por defecto es 1 hora y los valores por debajo de 5 minutos "should not be used in most situations" (no deberían usarse en la mayoría de los casos). Tu petición más larga debe caber dentro de la vida del token, o este puede caducar a mitad de camino.

Los tokens de renovación son la parte que rompe las configuraciones compartidas. Son de un solo uso: canjear uno devuelve un nuevo par de token de acceso y token de renovación. El intervalo de reutilización por defecto es de 10 segundos. Una reutilización fuera de esa ventana se trata como posible robo, y se termina toda la sesión. La guía de Next.js describe el síntoma: renovar en dos sitios "looks like users being signed out at random" (parece que se cierra la sesión de los usuarios al azar).

La regla práctica para un sistema de dos clientes es que cada cliente renueva su propia sesión, en un solo lugar. La app SwiftUI se encarga de su renovación. Un route handler que la atiende debe verificar el token que recibe y devolver un 401 cuando esté caducado, sin renovar nunca en nombre del cliente. En la web, el Proxy (middleware en Next.js 15 y anteriores) es el único lugar que renueva, y los Server Components reciben el resultado.

El cierre de sesión pide una comprobación más. Cerrar sesión borra la fila de la sesión, pero el JWT sigue siendo criptográficamente válido hasta exp. Cada token de acceso lleva un claim session_id que coincide con la clave primaria de auth.sessions, así que para acciones de alta sensibilidad la documentación sugiere confirmar que esa fila sigue existiendo. Si no existe, el usuario ha cerrado sesión.

Una app que estuvo sin conexión normalmente despertará con un token caducado. Prevé ese caso en el cliente, como se explica en apps de iOS offline-first: pon el trabajo en cola, renueva una vez y vuelve a enviarlo.

El esquema es el contrato de datos

El esquema de la base de datos es el contrato desde el que pueden generar ambos clientes. La CLI de Supabase lee tu esquema de Postgres y escribe los tipos. Para la web:

npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > database.types.ts

La referencia de la CLI para gen types lista una opción de lenguaje que acepta typescript, go, swift o python. Así que el mismo comando con el lenguaje fijado en swift produce tipos Swift desde el mismo esquema. Revisa la ayuda integrada de npx supabase gen types en la versión de tu CLI antes de integrarlo en una compilación.

La ventaja: una columna añadida, renombrada o que pasa a admitir nulos aparece como error de compilación en ambas plataformas, en lugar de como un fallo de decodificación en producción. Pasa el tipo Database generado a createClient<Database>() en la web, y usa los tipos Swift generados en la app.

Los tipos generados cubren filas. No cubren los cuerpos de petición y respuesta de tus propios route handlers de Next.js. Para esos, define la forma una sola vez en un archivo de esquema (OpenAPI o JSON Schema son opciones habituales) y genera ambos lados a partir de él. Ese paso es una recomendación, no una función documentada de Supabase.

Mantener los contratos sincronizados en CI

Los tipos se desajustan. Alguien cambia el esquema y olvida regenerar. La guía de Supabase para generar tipos describe una GitHub Action programada que regenera database.types.ts y hace commit y push solo cuando el archivo cambió. Una variante más estricta, que es una práctica habitual en equipos, ejecuta la misma generación en cada pull request y hace fallar la compilación si la salida versionada difiere. Ejecútala también para la salida de Swift, de modo que un cambio de esquema no pueda fusionarse con tipos obsoletos en ninguno de los dos lados.

Resumen

A octubre de 2026, el patrón es estable: un proveedor emite los tokens, el servidor Next.js verifica cada token bearer contra claves públicas, cada cliente renueva su propia sesión y un solo esquema genera los tipos para ambos. Si planeas una app de iOS junto a un producto web existente, nuestros equipos de desarrollo iOS y desarrollo web pueden dimensionar juntos el trabajo del backend compartido.

Preguntas frecuentes

¿Pueden la app SwiftUI y la app Next.js compartir un mismo inicio de sesión?

Sí, si ambas usan el mismo proyecto de proveedor de autenticación. Cada cliente inicia sesión en ese proyecto y recibe tokens para el mismo ID de usuario, así que la misma cuenta y las mismas reglas de base de datos se aplican en la web y en iOS.

¿Debe el servidor Next.js confiar en el objeto de usuario que envía la app Swift?

No. Confía solo en un token que el servidor verifique por sí mismo. Envía el token de acceso en una cabecera Authorization y verifica su firma y su caducidad en cada petición, como aconseja la documentación de Supabase para los datos de sesión en general.

¿Necesita la app Swift llamar a Next.js?

No siempre. Para lecturas y escrituras simples, el cliente Swift puede hablar directamente con la base de datos bajo seguridad a nivel de fila. Pasa por Next.js cuando necesites secretos solo de servidor, llamadas a terceros o lógica de negocio que no debe vivir en el dispositivo.