
# SDK de JavaScript para navegador

`@keenauth/js` es el núcleo para navegador de KeenAuth. Permite que los usuarios inicien sesión en su aplicación de una sola página, conserva sus tokens y realiza llamadas a su API. No tiene dependencias de tiempo de ejecución, y los componentes de React se basan en él. El paquete todavía no está publicado en npm.

## Iniciar sesión con redirección

El SDK es un cliente público estándar de OpenID Connect:

- Utiliza PKCE (S256) y tokens vinculados a DPoP.
- Comprueba `state`, `nonce` e `iss` en la devolución de llamada.
- Valida la firma y los claims del ID token con las claves de su entorno.

```ts
import { KeenAuth } from "@keenauth/js";

const auth = new KeenAuth({
  issuer: "https://acme.eu.keenauth.app", // your environment's issuer, exactly
  clientId: "app_01j9zzzzzzzzzzzzzzzzzzzz",
  redirectUri: "https://app.acme.example/callback",
  postLogoutRedirectUri: "https://app.acme.example/",
});

// On your sign-in button:
await auth.signInRedirect({ appState: "/orders" });

// On https://app.acme.example/callback:
const { appState } = await auth.handleRedirectCallback();
```

`handleRedirectCallback` acepta una respuesta solo una vez. Falla con un `KeenAuthError` cuando:

- `state` o `iss` no coinciden;
- el inicio de sesión pendiente tiene más de 10 minutos;
- el ID token no es válido.

Un error que envía el servidor se lanza como `KeenAuthOAuthError` con el código `error` de OAuth.

## Llamar a su API

Para obtener un access token para su API, pase su identificador como `resource` (RFC 8707). Se envía en la solicitud de autorización, en el intercambio del código y en cada renovación, y el `aud` del token es ese identificador:

```ts
const auth = new KeenAuth({
  issuer: "https://acme.eu.keenauth.app",
  clientId: "app_01j9zzzzzzzzzzzzzzzzzzzz",
  redirectUri: "https://app.acme.example/callback",
  resource: "https://api.acme.example", // your API's identifier
  scope: "openid profile email offline_access messages:read",
});
```

- El valor debe ser un URI absoluto sin fragmento (`#`). Se envía exactamente como lo escriba, así que use el identificador de la API tal como está registrado. Un valor no válido lanza un `KeenAuthError` con el código `config_invalid` desde el constructor.
- `signInRedirect({ resource })` lo sustituye para un único inicio de sesión.
- Sin `resource`, el servidor usa la API predeterminada de la aplicación, si hay una configurada; de lo contrario, no se conceden permisos de API.
- La opción también acepta un array, que se envía como parámetros `resource` repetidos, como permite RFC 8707. Un access token de KeenAuth tiene exactamente una audiencia, así que el servidor rechaza más de un valor con `invalid_target` (un `KeenAuthOAuthError`).

```ts
const res = await auth.authorizedFetch("https://api.acme.example/v1/orders");

// Or take the token yourself; it is refreshed when it is about to expire.
const token = await auth.getAccessToken();
```

`authorizedFetch` envía `Authorization: DPoP` con una prueba nueva vinculada al token. Si el servidor solicita un nonce de DPoP, reintenta una vez con ese nonce. Las llamadas concurrentes comparten una única solicitud de renovación, y cada renovación rota el refresh token. Para verificar estos tokens en su API, consulte [Verificación de tokens en su API](/docs/sdk/token-verification).

## Cerrar sesión

```ts
await auth.signOut();
```

`signOut` elimina los tokens y revoca el refresh token. Después envía el navegador al endpoint de cierre de sesión de su entorno, que lo devuelve a `postLogoutRedirectUri`.

## Dónde se guardan los tokens

- Los access, refresh e ID tokens se guardan solo en memoria, nunca en localStorage, sessionStorage, IndexedDB, cookies ni en la URL. Tras recargar la página, el usuario vuelve a iniciar sesión mediante redirección; el SDK no restaura la sesión.
- sessionStorage solo guarda los valores de un solo uso de un inicio de sesión en curso.
- La clave DPoP es una clave WebCrypto no extraíble, que por defecto se mantiene en memoria.

## Inicio de sesión integrado

El mismo objeto también proporciona a los componentes integrados un cliente de API frontal:

- `transactions` para los pasos de inicio de sesión;
- `me` para el perfil de la cuenta y `user_metadata`, correos electrónicos (con verificación de código), claves de acceso y sesiones;
- `webauthn` para los procesos de clave de acceso.

Estas llamadas usan las cookies de su entorno y una cabecera CSRF. El servidor solo las acepta desde el propio origen de inicio de sesión del entorno.

## Iniciar sesión sin redirección

En una página fuera de su origen de inicio de sesión, inicie la transacción con su `clientId`. El SDK la vincula entonces a su clave DPoP en lugar de a cookies:

```ts
let tx = await auth.transactions.create({ client_id: "app_01j9...", organization: "acme" });
tx = await auth.transactions.step("identifier", { identifier: "ada@acme.example" });
tx = await auth.transactions.step("password", { password });
if (tx.state === "completed") await auth.complete(tx); // { kind: "signed_in" }
```

- La aplicación debe ser una aplicación propia, y el origen de la página debe ser uno de sus orígenes web permitidos.
- Cada llamada incluye una prueba DPoP y ninguna cookie. Si el servidor solicita un nonce de DPoP, el SDK reintenta una vez con ese nonce.
- El SDK crea por sí mismo el verificador PKCE, `state` y `nonce`, y los guarda en memoria. `scope` y `resource` se toman de las opciones; puede pasar `organization` y `uiLocales` a `create`. Use un único valor de `resource` aquí.
- `complete` verifica la respuesta, intercambia el código de autorización con la misma clave DPoP y mantiene los tokens en memoria. La página no navega, y el código nunca se devuelve a su código.
- Cada llamada a `step`, con o sin `client_id`, envía la zona horaria del navegador en la cabecera `Idp-Zoneinfo`. Cuando el paso completa el inicio de sesión y el usuario no ha elegido una zona horaria, KeenAuth la guarda en `zoneinfo` del perfil, como lo hace la página de inicio de sesión alojada. Si el navegador no informa de una zona utilizable, la cabecera se omite y el paso no cambia.
- Si el inicio de sesión necesita una página que el usuario debe ver (consentimiento, términos legales, elección de una organización), el último paso falla con un `KeenAuthProblemError` cuyo código es `authorization_failed`. Para ese usuario, use `signInRedirect`.

## Llamadas de cuenta desde su propio origen

Una vez que el usuario ha iniciado sesión y el SDK tiene un refresh token (el ámbito predeterminado solicita `offline_access`), las llamadas `me` y `organizations` ya no necesitan la cookie de sesión del host de inicio de sesión:

- El SDK renueva con `resource=<issuer>/fapi` y obtiene un access token de corta duración para la API frontal. Está vinculado a DPoP y se mantiene solo en memoria, como los demás tokens.
- Cada llamada de cuenta envía `Authorization: DPoP <token>` y una prueba DPoP para esa llamada, sin cookie. Si el servidor solicita un nonce de DPoP, el SDK reintenta una vez con ese nonce; si el token ya no es válido, obtiene uno nuevo y reintenta una vez.
- El refresh token rota en cada renovación, así que el SDK ejecuta las renovaciones una tras otra.
- Esto solo funciona para una aplicación propia cuyos orígenes web permitidos incluyan el origen de su página.
- Sin tokens, el SDK usa la cookie de sesión como antes.

Consulte [Access token de la API frontal](/docs/api/fapi/front-api-token) para las reglas del servidor.

## Cambiar de organización

`organizations.switch(orgId)` cambia al usuario que ha iniciado sesión a otra organización sin cargar una página. Pase el ID `org_…` de una organización obtenida de `organizations.listMine()`:

```js
const { data } = await auth.organizations.listMine();
await auth.organizations.switch(data[1].id);
```

- Necesita lo mismo que las llamadas de cuenta anteriores: un refresh token, `redirectUri` y una aplicación propia cuyos orígenes web permitidos incluyan el origen de su página.
- El SDK envía una [solicitud de cambio](/docs/api/fapi/transactions/create) con el access token de la API frontal, intercambia el código con una prueba de la misma clave y mantiene los tokens nuevos, cuyo `org_id` es la organización. Los tokens que tenía se conservan hasta que todo lo anterior se complete.
- Si la organización necesita una página que el usuario debe ver (MFA, SSO, consentimiento), la llamada falla con un `KeenAuthProblemError` cuyo código es `authorization_failed`. Para ese usuario, use `signInRedirect({ organization })`.

## Límites actuales

- Las escrituras de perfil (`me.update`, `me.updateMetadata`) envían `If-Match` con el `ETag` de la última lectura o escritura del perfil. Si el perfil cambió en otro lugar entretanto, la escritura falla con un `KeenAuthProblemError` cuyo código es `precondition_failed`; vuelva a llamar a `me.get()` antes de reintentar.
- Los números de teléfono todavía no están disponibles: `me.phones` es `undefined`.
