SDK JavaScript pour navigateur
@keenauth/js est le cœur navigateur de KeenAuth. Il connecte les utilisateurs à votre application monopage, conserve leurs jetons et appelle votre API. Il n'a aucune dépendance d'exécution, et les composants React reposent sur lui. Le package n'est pas encore publié sur npm.
Se connecter avec une redirection
Le SDK est un client public OpenID Connect standard :
- Il utilise PKCE (S256) et des jetons liés par DPoP.
- Il vérifie
state,nonceetisslors du retour de redirection. - Il valide la signature et les revendications du jeton d'identité à l'aide des clés de votre environnement.
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 n'accepte une réponse qu'une seule fois. Il échoue avec une KeenAuthError lorsque :
stateouissne correspond pas ;- la connexion en attente date de plus de 10 minutes ;
- le jeton d'identité n'est pas valide.
Une erreur renvoyée par le serveur est levée sous la forme d'une KeenAuthOAuthError contenant le code error d'OAuth.
Appeler votre API
Pour obtenir un jeton d'accès pour votre API, transmettez son identifiant dans resource (RFC 8707). Il est envoyé dans la requête d'autorisation, l'échange du code et chaque renouvellement, et le aud du jeton correspond à cet identifiant :
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",
});
- La valeur doit être une URI absolue sans fragment (
#). Elle est envoyée exactement telle que vous l'écrivez ; utilisez donc l'identifiant de l'API tel qu'il est enregistré. Une valeur invalide lève uneKeenAuthErroravec le codeconfig_invaliddepuis le constructeur. signInRedirect({ resource })la remplace pour une seule connexion.- Sans
resource, le serveur utilise l'API par défaut de l'application, si elle est définie ; sinon, aucune autorisation d'API n'est accordée. - L'option accepte aussi un tableau, envoyé sous forme de paramètres
resourcerépétés, comme le permet la RFC 8707. Un jeton d'accès KeenAuth possède exactement une audience ; le serveur rejette donc plus d'une valeur avecinvalid_target(uneKeenAuthOAuthError).
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 envoie Authorization: DPoP avec une preuve récente liée au jeton. Si le serveur demande un nonce DPoP, la requête est relancée une fois avec ce nonce. Les appels simultanés partagent une seule requête de renouvellement, et chaque renouvellement remplace le jeton de rafraîchissement. Pour vérifier ces jetons dans votre API, consultez Vérifier les jetons dans votre API.
Se déconnecter
await auth.signOut();
signOut supprime les jetons et révoque le jeton de rafraîchissement. Il redirige ensuite le navigateur vers le point de terminaison de déconnexion de votre environnement, qui le renvoie vers postLogoutRedirectUri.
Où sont conservés les jetons
- Les jetons d'accès, de rafraîchissement et d'identité sont conservés uniquement en mémoire, jamais dans localStorage, sessionStorage, IndexedDB, des cookies ou l'URL. Après un rechargement de la page, l'utilisateur doit se connecter à nouveau par redirection ; le SDK ne restaure pas la session.
- sessionStorage contient uniquement les valeurs à usage unique d'une connexion en cours.
- La clé DPoP est une clé WebCrypto non extractible, conservée en mémoire par défaut.
Connexion intégrée
Le même objet fournit également aux composants intégrés un client d'API frontale :
transactionspour les étapes de connexion ;mepour le profil du compte et sesuser_metadata, les adresses e-mail (avec vérification par code), les clés d'accès et les sessions ;webauthnpour les cérémonies de clé d'accès.
Ces appels utilisent les cookies de votre environnement et un en-tête CSRF. Le serveur ne les accepte que depuis l'origine de connexion de l'environnement.
Se connecter sans redirection
Sur une page située hors de l'origine de connexion de votre environnement, démarrez la transaction avec votre clientId. Le SDK la lie alors à sa clé DPoP au lieu des cookies :
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'application doit être une application first-party, et l'origine de la page doit figurer parmi ses origines web autorisées.
- Chaque appel contient une preuve DPoP et aucun cookie. Si le serveur demande un nonce DPoP, le SDK relance la requête une fois avec ce nonce.
- Le SDK crée lui-même le vérificateur PKCE,
stateetnonce, et les conserve en mémoire.scopeetresourceproviennent des options ; vous pouvez transmettreorganizationetuiLocalesàcreate. Utilisez une seule valeur deresourceici. completevérifie la réponse, échange le code d'autorisation avec la même clé DPoP et conserve les jetons en mémoire. La page ne change pas de destination, et le code n'est jamais renvoyé à votre code.- Chaque appel
step, avec ou sansclient_id, envoie le fuseau horaire du navigateur dans l'en-têteIdp-Zoneinfo. Lorsque l'étape termine la connexion et que l'utilisateur n'a pas choisi de fuseau horaire, KeenAuth l'enregistre danszoneinfodu profil, comme le fait la page de connexion hébergée. Si le navigateur ne signale aucun fuseau utilisable, l'en-tête est omis et l'étape reste inchangée. - Si la connexion nécessite une page que l'utilisateur doit voir (consentement, conditions légales, choix d'une organisation), la dernière étape échoue avec une
KeenAuthProblemErrordont le code estauthorization_failed. UtilisezsignInRedirectpour cet utilisateur.
Appels au compte depuis votre propre origine
Une fois l'utilisateur connecté et le SDK en possession d'un jeton de rafraîchissement (la portée par défaut demande offline_access), les appels me et organizations n'ont plus besoin du cookie de session de l'hôte de connexion :
- Le SDK effectue le rafraîchissement avec
resource=<issuer>/fapiet obtient un jeton d'accès de courte durée pour l'API frontale. Il est lié par DPoP et conservé uniquement en mémoire, comme les autres jetons. - Chaque appel au compte envoie
Authorization: DPoP <token>et une preuve DPoP propre à cet appel, sans cookie. Si le serveur demande un nonce DPoP, le SDK relance la requête une fois avec ce nonce ; si le jeton n'est plus valide, il en obtient un nouveau et relance la requête une fois. - Le jeton de rafraîchissement est remplacé à chaque renouvellement, donc le SDK exécute les renouvellements les uns après les autres.
- Cela ne fonctionne que pour une application first-party dont les origines web autorisées incluent l'origine de votre page.
- Sans jetons, le SDK utilise le cookie de session comme auparavant.
Consultez Jeton d'accès à l'API frontale pour les règles côté serveur.
Changer d'organisation
organizations.switch(orgId) fait passer l'utilisateur connecté vers une autre organisation sans changer de page. Transmettez l'identifiant org_… d'une organisation renvoyée par organizations.listMine() :
const { data } = await auth.organizations.listMine();
await auth.organizations.switch(data[1].id);
- Il requiert les mêmes conditions que les appels au compte ci-dessus : un jeton de rafraîchissement,
redirectUriet une application first-party dont les origines web autorisées incluent l'origine de votre page. - Le SDK envoie une requête de changement avec le jeton de l'API frontale, échange le code avec une preuve de la même clé et conserve les nouveaux jetons, dont le
org_idcorrespond à l'organisation. Les jetons que vous possédiez restent en place tant que toutes ces étapes n'ont pas réussi. - Si l'organisation nécessite une page que l'utilisateur doit voir (MFA, SSO, consentement), l'appel échoue avec une
KeenAuthProblemErrordont le code estauthorization_failed. UtilisezsignInRedirect({ organization })pour cet utilisateur.
Limites actuelles
- Les écritures de profil (
me.update,me.updateMetadata) envoientIf-Matchavec l'ETagde la dernière lecture ou écriture du profil. Si le profil a été modifié entre-temps, l'écriture échoue avec uneKeenAuthProblemErrordont le code estprecondition_failed; appelez à nouveaume.get()avant de réessayer. - Les numéros de téléphone ne sont pas encore disponibles :
me.phonesvautundefined.