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,nonceeissnel callback. - Valida la firma e le claim dell'ID token rispetto alle chiavi del tuo ambiente.
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:
stateoissnon 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:
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 unKeenAuthErrorcon codiceconfig_invaliddal 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
resourceripetuti, come consente RFC 8707. Un access token di KeenAuth ha esattamente un'audience, quindi il server rifiuta più di un valore coninvalid_target(unKeenAuthOAuthError).
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.
Disconnettiti
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:
transactionsper i passaggi di accesso;meper il profilo dell'account euser_metadata, le email (con verifica del codice), le passkey e le sessioni;webauthnper 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:
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,
stateenoncee li mantiene in memoria.scopeeresourceprovengono dalle opzioni; puoi passareorganizationeuiLocalesacreate. Qui usa un solo valore diresource. completeverifica 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 senzaclient_id, invia il fuso orario del browser nell'headerIdp-Zoneinfo. Quando il passaggio completa l'accesso e l'utente non ha scelto un fuso orario, KeenAuth lo salva nelzoneinfodel 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
KeenAuthProblemErrorcon codiceauthorization_failed. Per quell'utente usasignInRedirect.
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>/fapie 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 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():
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,
redirectUrie un'applicazione di prima parte le cui origini web consentite includono l'origine della tua pagina. - L'SDK invia una richiesta di cambio 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
KeenAuthProblemErrorcon codiceauthorization_failed. Per quell'utente usasignInRedirect({ organization }).
Limiti attuali
- Le scritture del profilo (
me.update,me.updateMetadata) invianoIf-Matchcon l'ETagdell'ultima lettura o scrittura del profilo. Se il profilo è cambiato nel frattempo altrove, la scrittura fallisce con unKeenAuthProblemErrorcon codiceprecondition_failed; chiama di nuovome.get()prima di riprovare. - I numeri di telefono non sono ancora disponibili:
me.phonesèundefined.