
# SDK JavaScript per il browser

`@keenauth/js` è il core per il browser di KeenAuth. Accede gli utenti alla tua single-page app, conserva i loro token e chiama la tua API. Non ha dipendenze di runtime, e i componenti React si basano su di esso. Il pacchetto non è ancora pubblicato su npm.

## Accedi con un redirect

L'SDK è un client pubblico OpenID Connect standard:

- Usa PKCE (S256) e token vincolati tramite DPoP.
- Verifica `state`, `nonce` e `iss` nel callback.
- Valida la firma e le claim dell'ID token rispetto alle chiavi del tuo ambiente.

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

const auth = new KeenAuth({
  issuer: "https://acme.eu.keenauth.app", // l'issuer del tuo ambiente, esattamente
  clientId: "app_01j9zzzzzzzzzzzzzzzzzzzz",
  redirectUri: "https://app.acme.example/callback",
  postLogoutRedirectUri: "https://app.acme.example/",
});

// Sul tuo pulsante di accesso:
await auth.signInRedirect({ appState: "/orders" });

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

`handleRedirectCallback` accetta una risposta una sola volta. Fallisce con un `KeenAuthError` quando:

- `state` o `iss` non corrispondono;
- l'accesso in corso è più vecchio di 10 minuti;
- l'ID token non è valido.

Un errore inviato dal server viene sollevato come `KeenAuthOAuthError` con il codice `error` di OAuth.

## Chiama la tua API

Per ottenere un access token per la tua API, passa il suo identificatore come `resource` (RFC 8707). Viene inviato nella richiesta di autorizzazione, nello scambio del codice e in ogni refresh, e l'`aud` del token è quell'identificatore:

```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", // l'identificatore della tua API
  scope: "openid profile email offline_access messages:read",
});
```

- Il valore deve essere un URI assoluto senza frammento (`#`). Viene inviato esattamente come lo scrivi, quindi usa l'identificatore dell'API così come è registrato. Un valore non valido genera un `KeenAuthError` con codice `config_invalid` dal costruttore.
- `signInRedirect({ resource })` lo sovrascrive per un singolo accesso.
- Senza `resource`, il server usa l'API predefinita dell'applicazione, se ne è impostata una; altrimenti non vengono concessi scope API.
- L'opzione accetta anche un array, inviato come parametri `resource` ripetuti, come consente RFC 8707. Un access token di KeenAuth ha esattamente un'audience, quindi il server rifiuta più di un valore con `invalid_target` (un `KeenAuthOAuthError`).

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

// Oppure prendi tu il token; viene rinnovato quando sta per scadere.
const token = await auth.getAccessToken();
```

`authorizedFetch` invia `Authorization: DPoP` con una prova DPoP nuova, vincolata al token. Se il server chiede un nonce DPoP, riprova una volta con quel nonce. Le chiamate concorrenti condividono un'unica richiesta di refresh, e ogni refresh ruota il refresh token. Per verificare questi token nella tua API, vedi [Verifica dei token nella tua API](/docs/sdk/token-verification).

## Disconnettiti

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

`signOut` elimina i token e revoca il refresh token. Poi porta il browser all'endpoint di logout del tuo ambiente, che lo riporta a `postLogoutRedirectUri`.

## Dove vengono conservati i token

- Access token, refresh token e ID token sono conservati solo in memoria, mai in localStorage, sessionStorage, IndexedDB, cookie o nell'URL. Dopo un ricaricamento della pagina, l'utente deve accedere di nuovo con un redirect; l'SDK non ripristina la sessione.
- sessionStorage contiene solo i valori monouso di un accesso in corso.
- La chiave DPoP è una chiave WebCrypto non esportabile, mantenuta in memoria per impostazione predefinita.

## Accesso incorporato

Lo stesso oggetto fornisce anche ai componenti incorporati un client API frontend:

- `transactions` per i passaggi di accesso;
- `me` per il profilo dell'account e `user_metadata`, le email (con verifica del codice), le passkey e le sessioni;
- `webauthn` per le cerimonie con passkey.

Queste chiamate usano i cookie del tuo ambiente e un header CSRF. Il server le accetta solo dall'origine di login dell'ambiente stesso.

## Accedi senza redirect

In una pagina al di fuori della tua origine di login, avvia la transazione con il tuo `clientId`. L'SDK la lega poi alla sua chiave DPoP invece che ai cookie:

```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" }
```

- L'applicazione deve essere un'applicazione di prima parte, e l'origine della pagina deve essere una delle sue origini web consentite.
- Ogni chiamata porta una prova DPoP e nessun cookie. Se il server chiede un nonce DPoP, l'SDK riprova una volta con quel nonce.
- L'SDK crea da solo il verificatore PKCE, `state` e `nonce` e li mantiene in memoria. `scope` e `resource` provengono dalle opzioni; puoi passare `organization` e `uiLocales` a `create`. Qui usa un solo valore di `resource`.
- `complete` verifica la risposta, scambia il codice di autorizzazione con la stessa chiave DPoP e mantiene i token in memoria. La pagina non cambia posizione e il codice non viene mai restituito al tuo codice.
- Ogni chiamata a `step`, con o senza `client_id`, invia il fuso orario del browser nell'header `Idp-Zoneinfo`. Quando il passaggio completa l'accesso e l'utente non ha scelto un fuso orario, KeenAuth lo salva nel `zoneinfo` del profilo, come fa la pagina di accesso ospitata. Se il browser non riporta un fuso utilizzabile, l'header viene omesso e il passaggio resta invariato.
- Se l'accesso richiede una pagina che l'utente deve vedere (consenso, termini legali, scelta di un'organizzazione), l'ultimo passaggio fallisce con un `KeenAuthProblemError` con codice `authorization_failed`. Per quell'utente usa `signInRedirect`.

## Chiamate dell'account dalla tua origine

Una volta che l'utente ha effettuato l'accesso e l'SDK possiede un refresh token (lo scope predefinito richiede `offline_access`), le chiamate `me` e `organizations` non hanno più bisogno del cookie di sessione dell'host di accesso:

- L'SDK esegue il refresh con `resource=<issuer>/fapi` e ottiene un access token di breve durata per la Front API. È vincolato tramite DPoP e mantenuto solo in memoria, come gli altri token.
- Ogni chiamata all'account invia `Authorization: DPoP <token>` e una prova DPoP per quella chiamata, senza cookie. Se il server chiede un nonce DPoP, l'SDK riprova una volta con quel nonce; se il token non è più valido, ne ottiene uno nuovo e riprova una volta.
- Il refresh token ruota a ogni refresh, quindi l'SDK esegue i refresh uno dopo l'altro.
- Funziona solo per un'applicazione di prima parte le cui origini web consentite includono l'origine della tua pagina.
- Senza token, l'SDK usa il cookie di sessione come prima.

Vedi [Token di accesso della Front API](/docs/api/fapi/front-api-token) per le regole lato server.

## Cambiare organizzazione

`organizations.switch(orgId)` sposta l'utente già autenticato in un'altra organizzazione senza ricaricare la pagina. Passa l'ID `org_…` di un'organizzazione presa da `organizations.listMine()`:

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

- Richiede le stesse condizioni delle chiamate dell'account descritte sopra: un refresh token, `redirectUri` e un'applicazione di prima parte le cui origini web consentite includono l'origine della tua pagina.
- L'SDK invia una [richiesta di cambio](/docs/api/fapi/transactions/create) con il token della Front API, scambia il codice con una prova della stessa chiave e mantiene i nuovi token, il cui `org_id` è l'organizzazione. I token che avevi restano finché tutto questo non riesce.
- Se l'organizzazione richiede una pagina che l'utente deve vedere (MFA, SSO, consenso), la chiamata fallisce con un `KeenAuthProblemError` con codice `authorization_failed`. Per quell'utente usa `signInRedirect({ organization })`.

## Limiti attuali

- Le scritture del profilo (`me.update`, `me.updateMetadata`) inviano `If-Match` con l'`ETag` dell'ultima lettura o scrittura del profilo. Se il profilo è cambiato nel frattempo altrove, la scrittura fallisce con un `KeenAuthProblemError` con codice `precondition_failed`; chiama di nuovo `me.get()` prima di riprovare.
- I numeri di telefono non sono ancora disponibili: `me.phones` è `undefined`.
