# Primeros pasos con la autenticación gestionada de Relyt One

Una guía paso a paso para crear una aplicación Next.js multiusuario en Relyt One. Activa la autenticación gestionada en una rama, haz que los usuarios inicien sesión, verifica sus JWT con @relyt/auth y deja que la seguridad a nivel de fila de Postgres mantenga separadas las filas de cada usuario.

**En esta guía construimos una pequeña API de notas multiusuario sobre Relyt One. Los usuarios se registran e inician sesión en la URL de autenticación gestionada de la rama, la aplicación verifica sus tokens con `@relyt/auth` y la seguridad a nivel de fila de Postgres garantiza que cada usuario solo lea y escriba sus propias notas. En el camino cubrimos las partes que suelen fallar: orígenes de confianza, emisores que no coinciden, roles con `BYPASSRLS` y conexiones con pooling.**

Necesitas un proyecto de Relyt One, Node.js 20 o posterior, `psql` y unos treinta minutos. Todo lo que hay aquí también funciona con el [ejemplo TODO](https://github.com/relytcloud/relyt-auth-examples/tree/main/todo) de código abierto, que sigue el mismo patrón con más funciones (inicio de sesión social, equipos y roles, y un conjunto de pruebas). Si quieres el contexto de por qué la autenticación gestionada funciona así, lee primero [Autenticación gestionada para Relyt One, ya en vista previa](/blog/es/managed-auth-preview).

> **Nota**
>
> La autenticación gestionada está en vista previa. Los pasos coinciden con la consola actual; los nombres y los límites todavía pueden cambiar.

## Qué vamos a construir

![El navegador inicia sesión en la URL de autenticación y obtiene un JWT, la API lo verifica con JWKS y define app.user_id, y RLS filtra la tabla notes](/blog/assets/fa531cbef0f450e7.svg)

- Una tabla `notes` con un propietario por fila, protegida con seguridad a nivel de fila.
- Una ruta de Next.js `/api/notes` que lista y crea las notas de quien llama. Solo acepta `Authorization: Bearer <jwt>`.
- Un helper para el navegador que inicia la sesión de un usuario en la URL de autenticación y llama a la API con un JWT vigente.

Dos reglas sostienen todo el diseño, y cada paso de abajo se desprende de ellas:

1. **El id del usuario sale solo de un JWT verificado.** Nunca de un encabezado, de la query string ni del cuerpo de la solicitud.
2. **La base de datos también lo comprueba.** La API filtra por usuario y RLS vuelve a filtrar, así que un `WHERE` olvidado no puede filtrar las filas de nadie más.

## Paso 1: Activa la autenticación gestionada en una rama

En la consola, abre tu proyecto y elige la rama que usará tu aplicación. Mientras desarrollas, una rama secundaria de `main` es una buena opción: su configuración de autenticación, sus sesiones y sus claves de firma son propias, así que los experimentos nunca tocan los inicios de sesión de producción. (Si creas la rama secundaria después de activar la autenticación en `main`, empieza con una copia de los orígenes de confianza y de los usuarios finales de `main`, así que trátala como si tuviera datos reales de usuarios).

Ve a la pestaña **Auth** (autenticación) y elige **Enable auth** (activar la autenticación). La primera configuración despierta el cómputo de la rama y crea las tablas de autenticación; tarda alrededor de un minuto, así que deja la página abierta. Solo los propietarios y administradores de la organización pueden hacerlo.

Cuando el estado cambie a **Ready** (listo), el resumen muestra dos URL:

- **Auth URL** (URL de autenticación): donde tus usuarios inician sesión, y el valor que tu backend espera como emisor del token.
- **JWKS URL** (URL de JWKS): las claves públicas que tu backend usa para comprobar las firmas de los tokens.

Copia las dos. Los usuarios, las sesiones y las organizaciones ahora se guardan en el esquema `relyt_auth` de la base de datos de esta rama.

## Paso 2: Activa un método de inicio de sesión

En **Sign-in methods** (métodos de inicio de sesión), asegúrate de que **Email + password** (correo electrónico y contraseña) esté activado (lo está de forma predeterminada) y guarda. Es todo lo que necesita esta guía.

GitHub y Google funcionan igual una vez que tienes una OAuth App: copia la URL de callback que muestra la consola (apunta a la URL de autenticación, por ejemplo `<URL de autenticación>/api/auth/callback/github`) en la configuración del proveedor, luego pega el client ID y el secret en la consola y elige **Save & enable** (guardar y activar). El secret es de solo escritura. Una OAuth App de GitHub solo puede registrar una URL de callback, así que usa una OAuth App por rama.

## Paso 3: Confía en el origen de tu aplicación

La URL de autenticación es un origen distinto al de tu aplicación, así que el navegador envía una solicitud CORS cada vez que tu aplicación inicia la sesión de alguien. El servicio de autenticación solo responde a los orígenes que enumeres.

En **Redirects & origins** (redirecciones y orígenes), agrega el origen exacto de tu aplicación a **Trusted origins** (orígenes de confianza):

- `http://localhost:3000` mientras desarrollas;
- `https://app.example.com` (o donde despliegues) más adelante.

Sin rutas, sin barra final y sin comodines. Si más adelante agregas el inicio de sesión social, registra también en **Callback URLs** (URL de callback) la página que termina el inicio de sesión, como `http://localhost:3000/auth/callback`. Las URL de callback se comparan de forma exacta, y el origen de cada una también debe ser un origen de confianza.

> **Consejo**
>
> Si el inicio de sesión en el navegador falla con un error de red genérico, casi siempre la causa es este paso.

## Paso 4: Crea la tabla, la política y un rol de aplicación

Conéctate a la rama como propietario de la base de datos (`relytdb_owner` en la base de datos `relytdb`, con la contraseña que definiste al crear el proyecto) y crea la tabla:

```sql
CREATE TABLE notes (
  id         uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id    text NOT NULL,
  body       text NOT NULL CHECK (length(body) BETWEEN 1 AND 2000),
  created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX notes_user_id_idx ON notes (user_id, created_at DESC);
```

`user_id` guarda el claim `sub` del JWT del usuario, que es un id de texto.

Ahora activa la seguridad a nivel de fila y agrega una política que compare el propietario de cada fila con un valor que la aplicación definirá en cada transacción:

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

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

Qué hace cada parte:

- `ENABLE` activa RLS; `FORCE` hace que también se aplique al propietario de la tabla, salvo que el propietario tenga `BYPASSRLS`. En Relyt One, `relytdb_owner` lo tiene, así que sigue viendo todas las filas (el punto siguiente).
- `USING` filtra qué filas puede ver, actualizar o eliminar una consulta.
- `WITH CHECK` rechaza las inserciones y actualizaciones que crearían una fila de otra persona.
- `current_setting('app.user_id', true)` devuelve el valor, o nada si nunca se definió (el `true` significa «no lances un error»). Cuando falta, ninguna fila coincide, así que el código que olvida definirlo ve una tabla vacía en lugar de las notas de todos.

### Conéctate con un rol al que se le aplique RLS

Este es el paso que la gente se salta, y desactiva en silencio todo lo anterior.

Los superusuarios y los roles con `BYPASSRLS` ignoran todas las políticas. **Los usuarios de base de datos creados desde la consola de Relyt One tienen `BYPASSRLS`, y también `relytdb_owner`, el rol propietario con el que empieza todo proyecto.** Usa el propietario para crear tablas, nunca para atender solicitudes. Si tu aplicación se conecta con uno de ellos, la política nunca se ejecuta, y lo único que mantiene separados a los usuarios es el código de tu aplicación.

En su lugar, crea con SQL un rol dedicado para la aplicación:

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

`\password` es un comando de psql que pide la contraseña de forma interactiva, así que nunca termina en el historial de tu shell ni en los logs del servidor. Comprueba el resultado:

```sql
SELECT rolname, rolbypassrls, rolsuper FROM pg_roles WHERE rolname = 'notes_app';
--  rolname   | rolbypassrls | rolsuper
-- -----------+--------------+----------
--  notes_app | f            | f
```

Ambas columnas deben ser `f`. Un rol creado así inicia sesión tanto con la cadena de conexión directa como con la de pooling; comprobamos todo el patrón de este paso en una rama real, incluido que la política se mantiene detrás del pooler.

## Paso 5: Prepara la aplicación

Crea una aplicación Next.js 15 e instala el SDK y el driver de Postgres (`@relyt/auth/next` es compatible con Next.js 14.2.35+ y 15.5.28+, todavía no con la 16, así que fija la versión):

```bash
npx create-next-app@15 notes --ts --app
cd notes
npm install @relyt/auth pg
npm install -D @types/pg
```

Agrega la configuración a `.env.local`:

```bash
# La Auth URL del paso 1: solo el origen, sin barra final ni ruta.
RELYT_AUTH_ISSUER=https://<tu URL de autenticación>
RELYT_AUTH_JWKS_URL=https://<tu URL de JWKS>

# La misma URL de autenticación, para el navegador.
NEXT_PUBLIC_RELYT_AUTH_URL=https://<tu URL de autenticación>

# Conéctate como notes_app, el rol del paso 4, nunca con un usuario creado desde la consola.
DATABASE_URL=postgresql://notes_app:<contraseña>@<host>/relytdb?sslmode=require
```

El emisor debe ser exactamente el origen de la URL de autenticación, porque los tokens llevan ese valor tanto en `iss` como en `aud` y el SDK lo compara carácter por carácter. `sslmode=require` también importa: las conexiones a Relyt One usan TLS, y node-postgres no lo activa a menos que se lo pidas.

## Paso 6: Una sola entrada a la base de datos

Pon todo el acceso a la base de datos detrás de una única función que abra una transacción, defina `app.user_id` a partir del id de usuario verificado y solo entonces ejecute tus consultas.

```ts
// lib/db.ts
import { Pool, type PoolClient } from "pg";

const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 10 });

export async function withUser<T>(userId: string, fn: (db: PoolClient) => Promise<T>): Promise<T> {
  if (!userId) throw new Error("withUser: empty user id");
  const client = await pool.connect();
  try {
    await client.query("BEGIN");
    // `true` = local a esta transacción: una conexión del pool nunca lo arrastra a la siguiente solicitud.
    await client.query("SELECT set_config('app.user_id', $1, true)", [userId]);
    const result = await fn(client);
    await client.query("COMMIT");
    client.release();
    return result;
  } catch (e) {
    await client.query("ROLLBACK").catch(() => undefined);
    client.release(true); // descarta una conexión que podría estar rota en lugar de devolverla al pool
    throw e;
  }
}
```

El tercer argumento de `set_config` importa más de lo que parece. Sin él, el valor dura toda la sesión y, con un pool de conexiones, la siguiente solicitud que tome prestada esta conexión se ejecutaría como el usuario anterior. Con `true`, desaparece en el `COMMIT` o el `ROLLBACK`.

Lo mismo vale detrás del pooling por transacción de PgBouncer, porque el valor nunca sobrevive a la transacción.

## Paso 7: Verifica el token en cada solicitud

`getClaims` de `@relyt/auth/next` lee el bearer token, comprueba su firma EdDSA contra el JWKS, comprueba el emisor, la audiencia y la expiración, y devuelve los claims, o `null` si el token falta o no es válido.

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

export type Caller = { ok: true; userId: string } | { ok: false; response: Response };

export async function requireUser(req: Request): Promise<Caller> {
  try {
    const claims = await getClaims(
      req,
      { issuer: process.env.RELYT_AUTH_ISSUER!, jwksUrl: process.env.RELYT_AUTH_JWKS_URL },
      { cookie: false }, // las rutas de la API solo aceptan bearer tokens
    );
    if (!claims?.sub) {
      return { ok: false, response: Response.json({ error: "unauthorized" }, { status: 401, headers: { "www-authenticate": "Bearer" } }) };
    }
    return { ok: true, userId: claims.sub };
  } catch (e) {
    if (e instanceof RelytAuthUnavailableError) {
      // No se pudieron obtener las claves. No respondas 401: cerraría la sesión de todos durante un corte de red.
      return { ok: false, response: Response.json({ error: "auth_unavailable" }, { status: 503, headers: { "retry-after": "2" } }) };
    }
    throw e;
  }
}
```

Aceptar solo bearer tokens (`cookie: false`) mantiene la API libre de CSRF: un navegador nunca agrega por su cuenta un encabezado `Authorization` a una solicitud entre sitios.

## Paso 8: La API de notas

```ts
// app/api/notes/route.ts
import { requireUser } from "@/lib/auth";
import { withUser } from "@/lib/db";

export async function GET(req: Request) {
  const caller = await requireUser(req);
  if (!caller.ok) return caller.response;

  const notes = await withUser(caller.userId, async (db) => {
    const { rows } = await db.query(
      "SELECT id, body, created_at FROM notes WHERE user_id = $1 ORDER BY created_at DESC LIMIT 100",
      [caller.userId],
    );
    return rows;
  });
  return Response.json(notes);
}

export async function POST(req: Request) {
  const caller = await requireUser(req);
  if (!caller.ok) return caller.response;

  const input = await req.json().catch(() => null);
  const body = typeof input?.body === "string" ? input.body.trim() : "";
  if (body.length < 1 || body.length > 2000) return Response.json({ error: "invalid_body" }, { status: 400 });

  const note = await withUser(caller.userId, async (db) => {
    const { rows } = await db.query(
      "INSERT INTO notes (user_id, body) VALUES ($1, $2) RETURNING id, body, created_at",
      [caller.userId, body],
    );
    return rows[0];
  });
  return Response.json(note, { status: 201 });
}
```

Fíjate en que la consulta sigue diciendo `WHERE user_id = $1` aunque RLS filtraría de todos modos. Mantener ambos es deliberado: la consulta es rápida y explícita, y la política es la red de seguridad para el día en que alguien escriba una que no lo sea.

Fíjate también en lo que el handler no hace: nunca lee un id de usuario del cuerpo ni de la query string. El `user_id` que escribe es el del token.

## Paso 9: Inicia sesión desde el navegador

El navegador habla con dos lugares: con la URL de autenticación, para iniciar sesión, y con tu API, con un JWT.

```ts
// lib/auth-client.ts
"use client";
import { createAuthClient } from "@relyt/auth";

const authUrl = process.env.NEXT_PUBLIC_RELYT_AUTH_URL!;
const KEY = "notes.session-token";

export const authClient = createAuthClient({
  authUrl,
  sessionToken: () => localStorage.getItem(KEY),
  onSessionToken: (t) => localStorage.setItem(KEY, t),
});

export async function emailAuth(mode: "sign-in" | "sign-up", email: string, password: string) {
  const body = mode === "sign-up" ? { email, password, name: email.split("@")[0] } : { email, password };
  const res = await fetch(`${authUrl}/api/auth/${mode}/email`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(body),
    credentials: "omit",
  });
  if (!res.ok) throw new Error(`${mode} failed (HTTP ${res.status})`);
  const token = res.headers.get("set-auth-token");
  if (!token) throw new Error("No session token returned. Is this origin a trusted origin?");
  localStorage.setItem(KEY, token);
}

export async function api(path: string, init: RequestInit = {}) {
  const jwt = await authClient.getToken(); // en caché; se renueva antes de expirar
  const headers = new Headers(init.headers);
  headers.set("authorization", `Bearer ${jwt}`);
  return fetch(path, { ...init, headers });
}

export async function signOut() {
  const token = localStorage.getItem(KEY);
  if (token) {
    // Termina la sesión en la URL de autenticación, no solo en esta pestaña
    await fetch(`${authUrl}/api/auth/sign-out`, { method: "POST", headers: { authorization: `Bearer ${token}` }, credentials: "omit" }).catch(() => undefined);
  }
  localStorage.removeItem(KEY);
  authClient.clear();
}
```

Cómo encajan las piezas:

- `emailAuth` envía los datos a la URL de autenticación. La respuesta trae un **token de sesión** en el encabezado `set-auth-token`. Ese token es la sesión del usuario: quien lo tenga puede obtener JWT y llamar a los endpoints de la URL de autenticación como ese usuario hasta que la sesión termine, así que trátalo como una contraseña.
- `authClient.getToken()` intercambia el token de sesión por un **JWT** (15 minutos, EdDSA) y lo guarda en caché, renovándolo unos dos minutos antes de que expire.
- `api()` envía el JWT a tu API. El navegador nunca verifica tokens; eso le toca al servidor.

Guardar el token de sesión en `localStorage` mantiene esta guía corta, pero cualquier script que se ejecute en tu página puede leer lo que se guarda ahí, así que un bug de cross-site scripting lo expondría. Mantén los scripts de terceros fuera de las páginas que manejan el inicio de sesión y usa una Content Security Policy estricta. El ejemplo TODO muestra una configuración más robusta que además pone el JWT en una cookie `HttpOnly` para la navegación entre páginas.

Una página mínima para probarlo:

```tsx
// app/page.tsx
"use client";
import { useState } from "react";
import { api, emailAuth } from "@/lib/auth-client";

export default function Home() {
  const [notes, setNotes] = useState<{ id: string; body: string }[]>([]);
  const load = async () => setNotes(await (await api("/api/notes")).json());

  return (
    <main>
      <form
        onSubmit={async (e) => {
          e.preventDefault();
          const f = new FormData(e.currentTarget);
          await emailAuth("sign-up", String(f.get("email")), String(f.get("password")));
          await load();
        }}
      >
        <input name="email" type="email" required />
        <input name="password" type="password" required />
        <button>Sign up</button>
      </form>
      <button onClick={async () => { await api("/api/notes", { method: "POST", body: JSON.stringify({ body: "hello" }) }); await load(); }}>
        Add note
      </button>
      <ul>{notes.map((n) => <li key={n.id}>{n.body}</li>)}</ul>
    </main>
  );
}
```

Ejecuta `npm run dev`, registra dos usuarios en dos perfiles del navegador y agrega una nota con cada uno. Cada usuario solo ve las suyas.

## Paso 10: Comprueba el aislamiento desde la terminal

Verlo en un navegador está bien. Verlo negar el acceso cuando falta algo es mejor. Obtén un JWT para un usuario con `curl`:

```bash
AUTH=https://<tu URL de autenticación>
ORIGIN=http://localhost:3000

SESSION=$(curl -s -D - -o /dev/null "$AUTH/api/auth/sign-in/email" \
  -H "origin: $ORIGIN" -H "content-type: application/json" \
  -d '{"email":"alice@example.com","password":"<password>"}' \
  | awk 'tolower($1)=="set-auth-token:" {print $2}' | tr -d '\r')

JWT=$(curl -s "$AUTH/api/auth/token" -H "authorization: Bearer $SESSION" | sed 's/.*"token":"\([^"]*\)".*/\1/')

curl -s http://localhost:3000/api/notes -H "authorization: Bearer $JWT"
```

Luego prueba lo que debería fallar:

```bash
# Sin token: 401
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/api/notes

# Un token alterado: 401
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/api/notes -H "authorization: Bearer ${JWT}x"
```

Y comprueba directamente la parte del trato que le toca a la base de datos. Conectado como `notes_app`, sin definir `app.user_id`, la tabla está vacía sin importar cuántas notas existan:

```sql
SELECT count(*) FROM notes;            -- 0
BEGIN;
SELECT set_config('app.user_id', '<sub de Alice>', true);
SELECT count(*) FROM notes;            -- solo las notas de Alice
COMMIT;
```

Si la primera consulta devuelve todas las notas, tu aplicación está conectada con un rol que omite RLS. Vuelve al paso 4.

## Para ir más allá

### Inicio de sesión social

`startSocialSignIn` envía al usuario a GitHub o Google a través de la URL de autenticación, y `completeSocialSignIn` termina en tu página de callback usando un código de traspaso de un solo uso y PKCE, así que funciona sin cookies de terceros. El panel **Integration code** (código de integración) de la consola tiene ambos fragmentos, ya completados con las URL de tu rama. El README del ejemplo TODO enumera las reglas para la página de callback (debe ejecutar `completeSocialSignIn` antes que cualquier otra cosa, y el inicio de sesión debe empezar y terminar en el mismo origen).

### Equipos, roles y permisos

Cuando un usuario tiene una organización activa, su JWT lleva un claim `o`:

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

Define recursos y acciones en **Roles & permissions** (roles y permisos) en la consola, comprueba `o.perms` en tu API antes de cada operación y agrega una segunda política de RLS basada en una columna `org_id` (defínela con otro `set_config` en `withUser`) para las filas compartidas. El ejemplo TODO implementa exactamente esto, con todas las decisiones de permisos en un solo módulo.

## Solución de problemas

| Síntoma                                                                                                       | Causa probable                                                                                                                                 |
| ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| El inicio de sesión en el navegador falla con un error de red                                                 | El origen de la aplicación no está en **Trusted origins**, o tiene una ruta o una barra final                                                  |
| Todas las llamadas a la API devuelven 500 y el log del servidor muestra un error de `config` de `@relyt/auth` | `RELYT_AUTH_ISSUER` no es un origen sin más: tiene una barra final o una ruta, o usa `http` con un host que no es localhost                    |
| Todas las llamadas a la API devuelven 401 con un token que parece válido                                      | `RELYT_AUTH_ISSUER` es un origen distinto del que emitió el token, por ejemplo la URL de autenticación de otra rama                            |
| La API devuelve 503                                                                                           | No se pudo obtener el JWKS desde el servidor. Revisa el acceso de red saliente del servidor a la URL de JWKS                                   |
| La conexión a la base de datos falla o se queda colgada                                                       | A la cadena de conexión le falta `?sslmode=require`                                                                                            |
| Los usuarios pueden ver las filas de otros cuando una consulta olvida su filtro                               | La aplicación se conecta con un rol con `BYPASSRLS` (lo tienen los usuarios creados desde la consola y `relytdb_owner`). Usa el rol del paso 4 |
| Las consultas no devuelven ninguna fila                                                                       | `app.user_id` no se definió en la misma transacción que la consulta, o la consulta se ejecutó fuera de `withUser`                              |
| Una solicitud ve las filas de otro usuario, de forma intermitente                                             | `set_config` se llamó con `false` (para toda la sesión) en una conexión del pool. Usa `true`                                                   |

## Antes de salir a producción

- [ ] La aplicación se conecta con un rol `NOBYPASSRLS` dedicado (no `relytdb_owner`), y `rolbypassrls` es `f`.
- [ ] Cada tabla con datos de usuarios tiene RLS activado **y forzado**, con `USING` y `WITH CHECK`.
- [ ] Cada route handler llama a `requireUser`, incluso detrás de un middleware.
- [ ] Los ids de usuario salen solo de `claims.sub`.
- [ ] El cierre de sesión llama a `/api/auth/sign-out` de la URL de autenticación, y no se ejecutan scripts de terceros en las páginas que guardan el token de sesión.
- [ ] Una caída del JWKS responde 503, no 401.
- [ ] Los orígenes de producción están en **Trusted origins**, y las URL de callback de producción están registradas de forma exacta.
- [ ] Si guardas el JWKS en caché por tu cuenta, la caché dura menos de 960 segundos y vuelves a descargarlo cuando un token trae un id de clave desconocido, para que las rotaciones de claves funcionen. (`@relyt/auth` ya hace ambas cosas).
- [ ] Sabes que revocar las sesiones de un usuario en la consola deja válidos los JWT ya emitidos durante hasta 15 minutos, y tu aplicación maneja los casos en que eso importa.

## Próximos pasos

- Clona el [ejemplo TODO](https://github.com/relytcloud/relyt-auth-examples/tree/main/todo) para tener inicio de sesión social, equipos, roles y un conjunto de pruebas que comprueba todo lo anterior.
- Lee el [README de `@relyt/auth`](https://github.com/relytcloud/relyt-auth-js#readme) para conocer todas las opciones de `createAuthClient`, `getClaims` y `createVerifier`.
- Lee [Autenticación gestionada para Relyt One, ya en vista previa](/blog/es/managed-auth-preview) para conocer el diseño y el modelo de seguridad.

[Crear un proyecto gratis](/auth/signin?ref=blog-managed-auth-getting-started)
