# Iniciar sesión con UTILIA con el SDK JavaScript

Implementa «Iniciar sesión con UTILIA» con @utilia-os/sdk-js: URL de autorización con PKCE, canje del código, datos de la persona y cierre de sesión.

El SDK JavaScript de UTILIA OS (`@utilia-os/sdk-js`) resuelve el flujo OAuth por ti: genera el par PKCE y el `state`, construye la URL de autorización, canjea el código, guarda los tokens y los renueva antes de que caduquen. Esta guía muestra el inicio de sesión en una aplicación web y en Node.js.

## Antes de empezar

- Activa OAuth en tu aplicación externa, como explica [Activar OAuth en tu aplicación](https://os.utilia.ai/docs/developers/oauth/apps-panel). Para una aplicación web sin servidor, elige **Sin servidor (web o móvil)**; para Node.js con servidor, **Con servidor propio**.
- Registra como dirección de vuelta la ruta de tu aplicación que recibirá el código, por ejemplo `http://localhost:3000/callback`.
- Instala el SDK:

```bash
npm install @utilia-os/sdk-js
```

## Aplicación web

En el navegador hay una trampa: entre la ida a UTILIA OS y la vuelta, la página se recarga entera, y el almacenamiento por defecto del SDK vive en memoria. Para que el `code_verifier` y el `state` sobrevivan, dale un almacenamiento que use `sessionStorage`:

**Lo que tiene que sobrevivir a la recarga**

1. **getAuthorizationUrl()** (Tu página): Genera el code_verifier y el state y los guarda en tu almacenamiento.
2. **Ida y vuelta a UTILIA OS** (Navegador): La página se descarga: lo que estaba en memoria se pierde.
3. **handleCallback(code, undefined, state)** (Tu página de vuelta): Lee lo guardado, comprueba el state y canjea el código. Si no los encuentra: «No se encontró el estado PKCE pendiente».

1) **Crea el almacenamiento y el SDK**

   ```typescript
   import { UtiliaSDK } from "@utilia-os/sdk-js";
   import type { OAuthTokens, PendingOAuthState, TokenStorage } from "@utilia-os/sdk-js";

   class SessionTokenStorage implements TokenStorage {
     private read<T>(key: string): T | null {
       const raw = sessionStorage.getItem(key);
       return raw ? (JSON.parse(raw) as T) : null;
     }
     async getTokens() { return this.read<OAuthTokens>("utilia.tokens"); }
     async setTokens(tokens: OAuthTokens) { sessionStorage.setItem("utilia.tokens", JSON.stringify(tokens)); }
     async clearTokens() { sessionStorage.removeItem("utilia.tokens"); }
     async getPendingState() { return this.read<PendingOAuthState>("utilia.pending"); }
     async setPendingState(state: PendingOAuthState) { sessionStorage.setItem("utilia.pending", JSON.stringify(state)); }
     async clearPendingState() { sessionStorage.removeItem("utilia.pending"); }
   }

   export const sdk = new UtiliaSDK({
     baseURL: "https://os.utilia.ai/api",
     oauth: {
       clientId: "utilia_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
       redirectUri: "http://localhost:3000/callback",
       scopes: ["openid", "profile", "email"],
       tokenStorage: new SessionTokenStorage(),
     },
   });
   ```

   En una aplicación de navegador no pongas `clientSecret`: no se puede guardar en secreto. PKCE la protege.
2) **Lleva a la persona a autorizar**

   ```typescript
   const authUrl = await sdk.oauth.getAuthorizationUrl();
   window.location.href = authUrl;
   ```

   `getAuthorizationUrl()` genera el par PKCE y el `state`, los guarda en tu almacenamiento y devuelve la URL. Admite opciones como `scopes`, `resource`, `prompt`, `loginHint` y `nonce`.
3) **Canjea el código en la página de vuelta**

   ```typescript
   const params = new URLSearchParams(window.location.search);
   const code = params.get("code");
   const state = params.get("state");

   if (code) {
     // Con el state, el SDK comprueba que la respuesta es la de tu petición.
     const tokens = await sdk.oauth.handleCallback(code, undefined, state ?? undefined);
     console.log(tokens.scope); // lo que la persona concedió de verdad
   }
   ```

   Si `params.get("error")` vale `access_denied`, la persona no ha autorizado.
4) **Obtén los datos de la persona**

   ```typescript
   const user = await sdk.oauth.getUserInfo();

   console.log(user.sub);   // identificador estable
   console.log(user.name);
   console.log(user.email);
   ```

   `getUserInfo()` llama a `GET /api/oauth/userinfo` con el access token y lo renueva antes si está a punto de caducar.

## Node.js o línea de órdenes

En un proceso de Node.js, `FileTokenStorage` guarda los tokens y el estado pendiente en un archivo, así que sobreviven entre ejecuciones:

```typescript
import { UtiliaSDK, FileTokenStorage } from "@utilia-os/sdk-js";

const sdk = new UtiliaSDK({
  baseURL: "https://os.utilia.ai/api",
  oauth: {
    clientId: process.env.UTILIA_CLIENT_ID!,
    clientSecret: process.env.UTILIA_CLIENT_SECRET, // solo con servidor propio
    redirectUri: "http://localhost:8080/callback",
    scopes: ["openid", "profile", "email"],
    tokenStorage: new FileTokenStorage(".utilia-tokens.json"),
  },
});

if (await sdk.oauth.isAuthenticated()) {
  const user = await sdk.oauth.getUserInfo();
  console.log(`Hola, ${user.name}`);
} else {
  console.log("Abre esta URL:", await sdk.oauth.getAuthorizationUrl());
  // Después, con el code y el state de la redirección:
  // await sdk.oauth.handleCallback(code, undefined, state);
}
```

`isAuthenticated()` es asíncrono: sin `await` la condición siempre sería verdadera.

## Renovación y errores

El SDK renueva el access token automáticamente unos segundos antes de que caduque y guarda el refresh token nuevo que recibe en cada renovación, como exige la [rotación del refresh token](https://os.utilia.ai/docs/developers/oauth/refresh-token-rotation). Si varias peticiones necesitan renovar a la vez, comparten una sola renovación.

Si la renovación ya no es posible (la cadena caducó o se revocó, o la persona perdió el acceso), la llamada falla. Trátalo volviendo a pedir la autorización:

```typescript
try {
  const user = await sdk.oauth.getUserInfo();
} catch {
  await sdk.oauth.revokeToken(); // limpia lo guardado
  window.location.href = await sdk.oauth.getAuthorizationUrl();
}
```

## Cerrar sesión

```typescript
await sdk.oauth.logout();
```

Revoca el access token en UTILIA OS, lo que anula también el refresh token de esa emisión, y borra los tokens guardados.

## Qué puedes hacer con el token

El token de este flujo sirve para identificar a la persona y para llamar a los servidores MCP de UTILIA OS en su nombre (pide su `resource` y sus scopes al autorizar). Los servicios REST del SDK, como `sdk.tickets` o `sdk.users`, usan la API externa y se configuran con la clave de API de tu aplicación (`apiKey`), no con OAuth.

La referencia completa de la configuración OAuth del SDK está en [SDK JavaScript: OAuth](https://os.utilia.ai/docs/developers/sdk/oauth). Para el estilo del botón, [Botones y marca](https://os.utilia.ai/docs/developers/oauth/buttons-and-branding).
