
# Añadir inicio de sesión a una aplicación Next.js

Este inicio rápido añade el inicio de sesión de KeenAuth a una aplicación Next.js con App Router, usando el SDK `@keenauth/nextjs`. Su aplicación es el cliente OAuth y guarda todos los tokens en el servidor; el navegador recibe solo una cookie de sesión cifrada y HttpOnly. Usted añade las rutas de inicio de sesión, protege una página, llama a una API protegida con el token de acceso del usuario y prueba la aplicación sin conexión. App Router es el único enrutador que admite el SDK. El SDK se describe en [SDK de Next.js](/docs/sdk/nextjs).

La aplicación tiene estas páginas y rutas:

| Ruta | Qué hace |
|---|---|
| `/` | Página de inicio pública: un enlace para iniciar sesión, o el nombre del usuario y un botón para cerrar sesión |
| `/dashboard` | Requiere una sesión. Muestra el usuario y los mensajes que devuelve su API |
| `/api/messages` | Requiere una sesión. Llama a su API con el token de acceso del usuario |
| `/auth/login`, `/auth/callback`, `/auth/logout` | Servidas por el SDK |

## Antes de empezar

- Un entorno de KeenAuth. Su emisor tiene un aspecto como `https://your-environment.eu.keenauth.app`.
- Una aplicación web en el entorno ([Crear una aplicación](/docs/api/management/applications/create)) con `private_key_jwt` o un secreto de cliente como autenticación del cliente. En un entorno `development`, registre el URI de redirección `http://localhost/auth/callback` y el URI de redirección tras cerrar sesión `http://localhost/`; un URI de bucle invertido se registra sin puerto y coincide con cualquier puerto, como `3000`.
- Una API registrada en el entorno ([Crear una API](/docs/api/management/apis/create)) con el ámbito `messages:read`. Su `identifier`, por ejemplo `https://api.example.com`, es la audiencia de sus tokens de acceso. El [inicio rápido de Express](/docs/quickstarts/express) crea una API que acepta estos tokens.
- Node.js 22.18 o posterior.

## 1. Instalar

```sh
npm install @keenauth/nextjs next@16.4.2 react@19.3.1 react-dom@19.3.1
```

El SDK todavía no está publicado. Hasta entonces, el ejemplo enlaza con el código fuente del SDK en su `package.json`.

## 2. Configurar

La aplicación lee su configuración solo desde variables de entorno. Colóquelas en `.env.local`, que Next.js carga, y no suba nunca ese archivo al control de versiones.

| Variable | Obligatoria | Significado |
|---|---|---|
| `KEENAUTH_ISSUER` | sí | El emisor de su entorno |
| `KEENAUTH_CLIENT_ID` | sí | El `client_id` de la aplicación web |
| `KEENAUTH_CLIENT_KEY` | una de las dos | La clave privada (una JWK privada en JSON) para `private_key_jwt`, recomendada |
| `KEENAUTH_CLIENT_SECRET` | una de las dos | Un secreto de cliente |
| `APP_BASE_URL` | sí | La URL pública de la aplicación. El SDK construye desde ella el URI de redirección y todas las redirecciones. `http` solo para `localhost`, `127.0.0.1` o `[::1]` |
| `KEENAUTH_COOKIE_SECRET` | sí | Al menos 32 caracteres; sella la cookie de sesión. Genere uno con `openssl rand -base64 32` |
| `API_URL` | sí | La API a la que llama la aplicación, por ejemplo `http://localhost:8080` |
| `KEENAUTH_API_AUDIENCE` | sí | El identificador de esa API en KeenAuth |

`loadConfig` lee y comprueba estos valores. Se niega a continuar si falta alguno o si se definen ambas credenciales de cliente o ninguna, y sus mensajes de error nunca contienen un secreto.

```ts
export function loadConfig(env: Record<string, string | undefined>): AppConfig {
  const need = (name: string): string => {
    const value = env[name];
    if (!value) throw new Error(`${name} is required`);
    return value;
  };
```

## 3. Crear la instancia del SDK

`createAuth` crea la instancia del SDK, y el SDK comprueba cada opción en el momento de crearla. `getApp` crea una instancia por proceso en el primer uso, de modo que `next build` no necesita secretos.

```ts
/** Builds the SDK instance. The options are checked here, so a configuration error stops the app at start-up. */
export function createAuth(config: AppConfig, overrides: Partial<KeenAuthOptions> = {}): KeenAuth {
  return createKeenAuth({ ...config.auth, ...overrides });
}

let instance: { auth: KeenAuth; config: AppConfig } | undefined;

/** One instance per process, created on first use so that `next build` needs no secrets. */
export function getApp(): { auth: KeenAuth; config: AppConfig } {
  if (!instance) {
    const config = loadConfig(process.env);
    instance = { auth: createAuth(config), config };
  }
  return instance;
}
```

La configuración establece `resource` con el identificador de su API (RFC 8707). Sin él, el token de acceso no está destinado a su API y la API lo rechaza.

## 4. Añadir las rutas de inicio de sesión

Un único controlador de ruta comodín sirve `/auth/login`, `/auth/callback` y `/auth/logout`:

```ts
// Serves /auth/login, /auth/callback and /auth/logout.
export const GET = (request: Request) => getAuth().handlers.GET(request);
export const POST = (request: Request) => getAuth().handlers.POST(request);
```

El inicio de sesión usa el flujo de código de autorización con PKCE y una solicitud de autorización enviada previamente (pushed authorization request) cuando su entorno la ofrece. El callback comprueba `state`, `iss` y el ID token. Un `returnTo` que no sea una ruta de su aplicación se ignora.

## 5. Proteger páginas con el proxy

`proxy.ts` (`middleware.ts` antes de Next.js 16) renueva la sesión antes de que se renderice una página y protege las rutas que usted elija. Una solicitud de página sin sesión se redirige a `/auth/login` con `returnTo`; una solicitud a `/api/…` recibe `401`.

```ts
export async function proxy(request: NextRequest) {
  const result = await getAuth().proxy(request, { protect: isProtected });
  if (result.response) return result.response;
  const response = NextResponse.next({ request: { headers: result.requestHeaders } });
  result.apply(response);
  return response;
}
```

Las rutas que requieren una sesión están en una sola función:

```ts
export function isProtected(pathname: string): boolean {
  return pathname === "/dashboard" || pathname.startsWith("/dashboard/") || pathname.startsWith("/api/");
}
```

El patrón del proxy puede no cubrir una ruta, de modo que cada página y cada controlador de ruta que necesite un usuario también comprueba la sesión por sí mismo.

## 6. Llamar a su API

En un Server Component, lea la sesión y tome el token de acceso del almacén de cookies. El token permanece en el servidor; el navegador recibe solo el HTML.

```tsx
  // The proxy has already sent users without a session to the sign-in; check again here, because a matcher can miss a path.
  const session = await auth.getSession(cookieStore);
  if (!session) return <a href="/auth/login?returnTo=/dashboard">Sign in</a>;

  // The access token stays on the server. The browser receives only the HTML below.
  const response = await fetchMessages(config.apiUrl, await auth.getAccessToken(cookieStore));
```

`session.user` contiene las notificaciones del ID token, como `subject`, `email` y `name`. La sesión nunca contiene un token.

La llamada en sí es un `fetch` normal con un token de portador:

```ts
export async function fetchMessages(apiUrl: string, accessToken: string, fetchImpl: typeof fetch = fetch): Promise<Response> {
  return fetchImpl(new URL("/api/messages", apiUrl), {
    headers: { Authorization: `Bearer ${accessToken}` },
    cache: "no-store",
  });
}
```

Un controlador de ruta también puede establecer cookies, así que usa `updateSession`, que renueva un token de acceso próximo a caducar:

```ts
  // A route handler can set cookies, so updateSession also refreshes an access token that is about to expire.
  const { accessToken, apply } = await auth.updateSession(request);
  if (!accessToken) return new Response(null, { status: 401 });
```

No pase el token de acceso a un Client Component.

## 7. Cerrar sesión

Cerrar sesión es un formulario que envía una solicitud POST a `/auth/logout`. El SDK elimina la cookie de sesión, revoca el refresh token y envía el navegador al endpoint de cierre de sesión de su entorno, que lo devuelve a su aplicación. Una solicitud que el navegador marca como procedente de otro sitio se rechaza.

```tsx
      <form action="/auth/logout" method="post">
        <button type="submit">Sign out</button>
      </form>
```

## 8. Ejecutar la aplicación

Inicie la API del inicio rápido de Express (o cualquier API que acepte los tokens) y ejecute la aplicación:

```sh
npm run dev
```

Abra `http://localhost:3000`, elija **Sign in**, inicie sesión en KeenAuth y abra el panel.

## 9. Probar sin red

Las pruebas no necesitan red ni una cuenta de KeenAuth. Un `fetch` simulado hace de servidor de autorización (descubrimiento y la solicitud de autorización enviada previamente), y Node ejecuta directamente las pruebas en TypeScript.

```ts
test("proxy: a page request without a session is redirected to sign-in with returnTo", async () => {
  const { auth } = app();
  const result = await auth.proxy(new Request(`${APP}/dashboard?tab=1`), { protect: isProtected });
  assert.equal(result.response?.status, 307);
```

```sh
npm test
npm run deps    # pinned versions, lockfile sources and licences
```

Las pruebas comprueban que la configuración se valida al arrancar, que una página sin sesión redirige al inicio de sesión y que una ruta de API responde `401`, que el inicio de sesión envía una solicitud PKCE S256 enviada previamente y establece una cookie HttpOnly sellada, que se rechaza un callback con un state desconocido, que se rechaza un cierre de sesión desde otro sitio y que el token de acceso llega a la API en la cabecera `Authorization`.

El inicio de sesión completo con un navegador real necesita un entorno de KeenAuth. No forma parte de las pruebas sin conexión.

## Detrás de un proxy o balanceador de carga

Defina `APP_BASE_URL` con la URL pública de la aplicación. El SDK nunca lee `Host` ni `X-Forwarded-*` para construir redirecciones. Con varias instancias del servidor, dos de ellas podrían renovar a la vez el mismo refresh token; consulte [Límites actuales](/docs/sdk/nextjs#límites-actuales).

## Próximos pasos

- [SDK de Next.js](/docs/sdk/nextjs): todas las opciones, la cookie de sesión y los límites actuales.
- [Proteger una API de Express](/docs/quickstarts/express): la API a la que llama esta aplicación.
- [Verificación de tokens](/docs/sdk/token-verification): el verificador bajo el middleware de su API.
