# Autenticación gestionada para Relyt One, ya en vista previa

Registro, inicio de sesión, sesiones y organizaciones para los usuarios de tu aplicación, funcionando junto a tu rama y guardando los usuarios en tu propia base de datos. Los tokens son JWT EdDSA de corta duración que verificas con JWKS, y la seguridad a nivel de fila hace el resto.

**La autenticación gestionada ya está disponible en vista previa en Relyt One. Actívala en una rama y esa rama obtiene su propio servicio de inicio de sesión para los usuarios de tu aplicación: correo electrónico y contraseña, GitHub y Google, sesiones, organizaciones con roles y permisos, y claves de firma que puedes rotar. Los usuarios viven en un esquema `relyt_auth` dentro de la propia base de datos de la rama, tu backend verifica JWT de corta duración contra una URL de JWKS y la seguridad a nivel de fila de Postgres mantiene las filas de un usuario fuera del alcance de otro.**

Activarla toma alrededor de un minuto, y el SDK de código abierto [`@relyt/auth`](https://github.com/relytcloud/relyt-auth-js) y un [ejemplo multiusuario](https://github.com/relytcloud/relyt-auth-examples/tree/main/todo) completo están en GitHub. Si quieres ir directo a la práctica, la guía paso a paso es [Primeros pasos con la autenticación gestionada de Relyt One](/blog/es/managed-auth-getting-started).

Este artículo trata de por qué la construimos así.

## La autenticación es lo primero que necesita toda aplicación, y lo primero que se desalinea

En cuanto una aplicación guarda algo personal, necesita tres cosas: una forma de que los usuarios inicien sesión, una forma de que el backend sepa quién llama y la garantía de que un usuario nunca vea los datos de otro.

La forma habitual de lograrlo es elegir un proveedor de autenticación, conectarlo a tu aplicación y, a partir de ahí, mantener dos sistemas de acuerdo durante el resto de la vida de la aplicación. Ahí es donde se complica.

- **Tus usuarios viven en otro lado.** El proveedor de autenticación tiene su propia tabla de usuarios, en su propia base de datos, con sus propios ids. Cada clave foránea de tus datos hacia un usuario cruza la frontera entre sistemas. Unir «pedidos» con «usuarios» implica una llamada a una API o un job de sincronización.
- **Los entornos no coinciden.** Tu base de datos tiene una copia de producción, una de staging y una rama por pull request. Tu proveedor de autenticación suele tener un tenant de producción y quizás uno de pruebas. Los entornos de vista previa terminan iniciando sesión contra los usuarios de producción, o contra un tenant vacío con el que no coincide ningún dato de prueba.
- **La autorización se aplica en un solo lugar.** El proveedor le dice a tu API quién llama. Si esa persona puede ver una fila determinada se comprueba en el código de la aplicación, consulta por consulta. Olvida un `WHERE user_id = ...` y tienes una fuga de datos.

Los agentes de programación agudizan los tres problemas. Un agente que monta una aplicación nueva conecta la autenticación a toda prisa, contra el tenant que esté configurado, y comprueba los permisos donde se acuerda de hacerlo.

## Autenticación que vive en la rama

La autenticación gestionada parte de la misma idea que el resto de Relyt One: lo que pertenece a tus datos debe vivir con tus datos.

![El navegador inicia sesión en la URL de autenticación de la rama, obtiene un JWT de corta duración y llama a tu API, que lo verifica con JWKS y define app.user_id para la seguridad a nivel de fila en la misma base de datos donde vive relyt_auth](/blog/assets/fa531cbef0f450e7.svg)

Cuando activas la autenticación gestionada en una rama:

- **La rama obtiene su propia URL de autenticación.** Tus usuarios se registran e inician sesión ahí. Es un origen distinto al de tu aplicación: el formulario de inicio de sesión envía los datos directamente a ella, así que tu backend nunca recibe una contraseña ni guarda ninguna.
- **Los usuarios, las sesiones y las organizaciones se guardan en tu base de datos**, en el esquema `relyt_auth` de esa rama. Son filas que puedes consultar con SQL, junto a las filas que les pertenecen.
- **La rama obtiene una URL de JWKS** que publica las claves públicas de sus tokens. Tu backend solo necesita la mitad pública.
- **La configuración de autenticación pertenece a la rama.** Cada rama tiene sus propios métodos de inicio de sesión, orígenes y claves, y se eliminan cuando se elimina la rama o el proyecto. Una rama secundaria de una rama con autenticación empieza como una copia: hereda los orígenes de confianza y la configuración de correo electrónico, y su base de datos contiene una copia de los usuarios finales de la rama de origen, mientras que las sesiones y las claves de firma no se trasladan.

El servicio está construido sobre [Better Auth](https://www.better-auth.com/), así que los endpoints de inicio de sesión y el modelo de sesiones te resultarán familiares si ya lo usaste.

## Cómo se autentica una solicitud

El flujo es el mismo para todas las aplicaciones, use el framework que use.

1. **Iniciar sesión en la URL de autenticación.** El navegador envía el correo electrónico y la contraseña (o pasa por GitHub o Google) a la URL de autenticación de la rama y recibe un token de sesión.
2. **Cambiar la sesión por un JWT.** El SDK intercambia la sesión por un JSON Web Token que dura 15 minutos, firmado con EdDSA. Guarda el token en caché y lo renueva antes de que expire.
3. **Llamar a tu API con el JWT.** El navegador envía `Authorization: Bearer <jwt>`. Nunca verifica el token por su cuenta.
4. **Verificar en el servidor.** Tu API comprueba la firma contra el JWKS de la rama, además del emisor, la audiencia y la expiración, y toma el id del usuario de `sub`.
5. **Dejar que Postgres lo haga cumplir.** Tu API define el id del usuario para la transacción, y las políticas de seguridad a nivel de fila deciden qué filas puede ver ese usuario.

En código, los pasos 4 y 5 son unas pocas líneas:

```ts
import { getClaims, RelytAuthUnavailableError } from "@relyt/auth/next";

export async function GET(req: Request) {
  let claims;
  try {
    claims = await getClaims(req, { issuer: process.env.RELYT_AUTH_ISSUER! });
  } catch (e) {
    // No se pudieron obtener las claves: 503, para que un corte de red no cierre la sesión de todos
    if (e instanceof RelytAuthUnavailableError) return new Response(null, { status: 503 });
    throw e;
  }
  if (!claims) return new Response(null, { status: 401 });

  const rows = await db.transaction(async (tx) => {
    await tx.execute(sql`select set_config('app.user_id', ${claims.sub}, true)`);
    return tx.select().from(notes); // solo las notas de este usuario
  });
  return Response.json(rows);
}
```

```sql
ALTER TABLE notes ENABLE ROW LEVEL SECURITY;
ALTER TABLE notes FORCE ROW LEVEL SECURITY;

CREATE POLICY own_notes ON notes FOR ALL
  USING (owner_id = current_setting('app.user_id', true))
  WITH CHECK (owner_id = current_setting('app.user_id', true));
```

El valor es local a la transacción, así que una conexión del pool nunca lo arrastra a la siguiente solicitud. Si tu código olvida definirlo, `current_setting` no devuelve nada y la política no coincide con ninguna fila. Una comprobación que falta significa sin acceso, nunca una fuga.

## Qué incluye la vista previa

### Métodos de inicio de sesión

- **Correo electrónico y contraseña.**
- **GitHub y Google.** Pegas en la consola el client ID y el secret de tu OAuth App; el secret es de solo escritura y nunca se vuelve a mostrar. El inicio de sesión social entre orígenes usa un código de traspaso de un solo uso con PKCE, así que funciona sin cookies de terceros.

### Redirecciones y orígenes

La URL de autenticación solo responde a navegadores de los **orígenes de confianza** que enumeres, como `https://app.example.com` o `http://localhost:3000`, sin comodines. El inicio de sesión social solo redirige de vuelta a **URL de callback** registradas de forma exacta. Ambas listas son por rama, así que una rama de vista previa puede confiar en `https://pr-128.example.dev` sin que producción también confíe en ella.

### Tokens y claves

- **Los JWT duran 15 minutos** y se firman con EdDSA. El SDK solo acepta EdDSA, así que los tokens firmados con cualquier otro algoritmo se rechazan.
- **Las claves de firma se rotan cuando lo pidas.** Después de una rotación, la clave nueva firma el siguiente token, las claves anteriores permanecen en el JWKS durante 960 segundos (unos 16 minutos) para que los tokens ya emitidos sigan verificándose, y los nodos edge toman el nuevo conjunto de claves en unos 30 segundos. Durante ese periodo de gracia se conservan como máximo tres claves anteriores, así que nuevas rotaciones dentro de él se rechazan hasta que una expire. Los verificadores deben volver a descargar el JWKS cuando vean un id de clave desconocido; `@relyt/auth` lo hace.
- **Las sesiones se pueden revocar** para un usuario o para todos los de la rama. Una sesión revocada ya no puede renovarse ni intercambiarse por tokens nuevos. Los JWT ya emitidos siguen siendo válidos hasta que expiran, como máximo 15 minutos, así que si el acceso debe cortarse al instante, compruébalo también en tu aplicación.

### Organizaciones, roles y permisos

Los usuarios pueden crear organizaciones, y los propietarios y administradores pueden invitar miembros mediante los endpoints de organización de la URL de autenticación (la vista previa no envía correos de invitación ni de verificación, así que la persona invitada debe iniciar sesión con un correo electrónico verificado, lo que hoy significa GitHub o Google). Cada miembro tiene un rol, y en la consola defines tus propios **recursos y acciones**, por ejemplo un recurso `todo` con `create`, `read`, `update` y `delete`, y los otorgas a los roles. Los roles integrados son `owner`, `admin` y `member`; puedes agregar hasta 16 roles personalizados y 32 recursos personalizados.

Cuando un usuario tiene una organización activa, el JWT la incluye:

```json
"o": {
  "id": "…",
  "slug": "acme",
  "role": "member",
  "perms": ["todo:create", "todo:read", "todo:update"]
}
```

Tu API comprueba `o.perms` para la operación, y una política de RLS sobre `org_id` decide qué filas del equipo son visibles. La lista de permisos tiene que caber en el token, así que la consola limita el total a 64 pares `resource:action` y 1 KiB; si alguna vez no cabe, `perms` es `null` y tu API debe rechazar la solicitud en lugar de suponer.

### Operación

- **Users** (usuarios) muestra los usuarios finales de la rama, si su correo electrónico está verificado, su rol, sus sesiones activas y su última actividad, con búsqueda sobre la lista cargada.
- **Integration code** (código de integración) muestra las variables de entorno y los fragmentos para el middleware de Next.js, los route handlers, el cliente del navegador y el inicio de sesión social, ya completados con las URL de la rama.
- **Disable auth** (desactivar la autenticación) detiene los inicios de sesión sin eliminar usuarios, configuración ni claves. Vuelve a activarla y todo estará donde lo dejaste.

## Algo a vigilar: BYPASSRLS

La seguridad a nivel de fila solo te protege si tu aplicación se conecta con un rol al que se le aplica. Los usuarios de base de datos creados desde la consola de Relyt One tienen `BYPASSRLS`, y Postgres omite en silencio todas las políticas para ellos. Para cualquier aplicación que dependa de RLS, crea un rol de aplicación dedicado con SQL:

```sql
CREATE ROLE app_user LOGIN NOBYPASSRLS NOSUPERUSER NOCREATEROLE NOCREATEDB;
\password app_user
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON notes TO app_user;
```

`\password` es un comando de psql que pide la contraseña de forma interactiva, así que nunca queda en el historial de tu shell. Después comprueba el rol con `SELECT rolbypassrls FROM pg_roles WHERE rolname = 'app_user';`, que debe devolver `f`. El conjunto de pruebas de la aplicación de ejemplo tiene un caso que muestra qué pasa si te equivocas en esto.

## Límites de la vista previa

La autenticación gestionada es una vista previa, y algunas cosas son deliberadamente pequeñas o faltan mientras aprendemos cómo la usa la gente:

- Solo los propietarios y administradores de la organización pueden activar la autenticación y cambiar su configuración.
- Las ramas secundarias no copian las credenciales del inicio de sesión social, y una OAuth App de GitHub solo puede registrar una URL de callback, así que usa una OAuth App por rama.
- Todavía no se envían correos: ni de verificación, ni de restablecimiento de contraseña, ni de invitación.
- Los nombres, los límites y el diseño de la consola todavía pueden cambiar antes de la disponibilidad general.

## Pruébala

1. Abre una rama en la consola, ve a **Auth** (autenticación) y elige **Enable auth** (activar la autenticación).
2. Sigue [Primeros pasos con la autenticación gestionada de Relyt One](/blog/es/managed-auth-getting-started) para agregar inicio de sesión, verificación de tokens y RLS a una aplicación.
3. O clona el [ejemplo TODO](https://github.com/relytcloud/relyt-auth-examples/tree/main/todo), que hace todo esto con TODO personales y de equipo, y un conjunto de pruebas.

Cuéntanos qué falla. La vista previa es el momento de cambiar las cosas.

[Probar la autenticación gestionada](/auth/signin?ref=blog-managed-auth-preview)
