SDK de gestion pour Node.js

@keenauth/node est le SDK TypeScript de l'API de gestion KeenAuth. Il nécessite Node.js 22 ou une version ultérieure et n'a aucune dépendance d'exécution. Il n'est pas encore publié sur npm.

Le package dispose des points d'entrée suivants :

Import Utilisation
@keenauth/node Le client de l'API de gestion, les jetons de gestion, la pagination et les erreurs
@keenauth/node/webhooks La vérification des signatures des webhooks, sans charger le client de l'API
@keenauth/node/bff La connexion pour les applications serveur : Express et Fastify
@keenauth/node/middleware La protection de votre API avec des jetons porteurs
@keenauth/node/testing Le kit de test hors ligne

Créer un client

Avec une clé d'API secrète ou restreinte :

import { KeenAuth } from "@keenauth/node";

const keenauth = new KeenAuth({
  baseUrl: "https://api.eu.keenauth.com/v1",
  apiKey: process.env.KEENAUTH_API_KEY!,
});

const user = await keenauth.users.getUser({ userId: "usr_01j9zzzzzzzzzzzzzzzzzzzz" });

baseUrl est l'adresse de l'API de votre région suivie de /v1. Elle doit utiliser https ; http n'est accepté que pour localhost. La clé n'est envoyée que dans l'en-tête Authorization et uniquement à cette adresse. Les redirections ne sont pas suivies.

S'authentifier sans clé d'API

Une application management peut appeler l'API avec un jeton de courte durée au lieu d'une clé statique. L'application signe une assertion client avec sa clé privée (private_key_jwt, RFC 7523), et le SDK l'échange auprès du point de terminaison de jeton de votre environnement.

  1. Dans le tableau de bord, créez une application de type management ainsi qu'un enregistrement de client de gestion avec les autorisations dont elle a besoin (voir Créer un client de gestion).
  2. Générez une paire de clés et enregistrez la clé publique comme identifiant de type public_key de l'application. Par exemple, pour ES256 : openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out management-key.pem.
  3. Conservez la clé privée dans votre coffre de secrets. Transmettez-la au SDK :
import { readFileSync } from "node:fs";
import { KeenAuth } from "@keenauth/node";

const keenauth = new KeenAuth({
  baseUrl: "https://api.eu.keenauth.com/v1",
  credentials: {
    issuer: "https://acme.eu.keenauth.app", // l'émetteur de votre environnement, à l'identique
    clientId: "app_01j9zzzzzzzzzzzzzzzzzzzz",
    privateKey: readFileSync("management-key.pem", "utf8"), // PEM, un JWK privé ou un KeyObject
    scope: ["users:read", "users:write"], // facultatif ; par défaut : les scopes par défaut de l'application
  },
});

Ce que fait le SDK :

Une requête de jeton en échec lève une TokenError contenant le code OAuth (par exemple invalid_client ou invalid_scope), la description et le status. Une erreur de configuration, comme une clé publique fournie à la place d'une clé privée, est levée lors de la création du client.

apiKey et credentials ne peuvent pas être utilisés ensemble.

Nouvelles tentatives

Le SDK relance une requête au maximum 3 fois au total lorsque l'API répond 429, 502, 503 ou 504, ou lorsque la connexion échoue. Il ne relance que les requêtes GET, PUT et DELETE, ainsi que les requêtes POST qui portent un Idempotency-Key. Une requête PATCH qui porte If-Match n'est relancée que sur 429 : le limiteur de débit refuse la requête avant son exécution, et If-Match empêche qu'une modification soit appliquée deux fois. Tout autre PATCH n'est jamais relancé.

Le SDK ajoute un Idempotency-Key aléatoire à chaque POST dont l'opération accepte cet en-tête, afin qu'une création relancée ne produise pas un second objet. Pour rendre vos propres nouvelles tentatives sûres également, transmettez vous-même la même clé :

await keenauth.users.createUser({
  userCreateRequest: { email: "ada@example.com" },
  idempotencyKey: "signup-7d1f0c2a-0d7e-4c55",
});

Pagination

Les points de terminaison de liste renvoient une seule page : data et listMetadata avec les curseurs before et after. paginate parcourt les curseurs à votre place :

import { paginate } from "@keenauth/node";

for await (const user of paginate((p) => keenauth.users.listUsers(p), { limit: 100 })) {
  console.log(user.id);
  if (user.email === "ada@example.com") break; // s'arrête sans charger la page suivante
}

Erreurs

Une réponse d'erreur devient une ProblemError (RFC 9457) avec status, code, title, detail, requestId et les errors par champ. Vérifiez code, et non le message. Les codes que le SDK ne connaît pas restent disponibles sous forme de chaînes.

import { ProblemError } from "@keenauth/node";

try {
  await keenauth.users.getUser({ userId });
} catch (e) {
  if (e instanceof ProblemError && e.code === "not_found") {
    // …
  }
}

Une réponse sans document de problème, comme une page HTML provenant d'un proxy, devient la ResponseError générée, avec la response brute.

Vérifier les webhooks

KeenAuth signe les webhooks selon le schéma Standard Webhooks v1. Vérifiez la signature à partir du corps brut de la requête, avant de l'analyser :

import express from "express";
import { Webhook, WebhookVerificationError } from "@keenauth/node/webhooks";

const webhook = new Webhook(process.env.KEENAUTH_WEBHOOK_SECRET!); // idp_whsec_…

const app = express();
app.post("/webhooks/keenauth", express.raw({ type: "application/json" }), (req, res) => {
  let event;
  try {
    event = webhook.verify(req.body, req.headers);
  } catch (e) {
    if (e instanceof WebhookVerificationError) return res.status(400).send(e.kind);
    throw e;
  }
  // traiter event.type, puis répondre rapidement
  res.sendStatus(204);
});

Les webhooks peuvent arriver plusieurs fois. Utilisez webhook-id pour ignorer les doublons.

Événements typés et revendications

@keenauth/node/events dispose d'un type pour chaque événement client, généré à partir des schémas publics des événements. parseEvent vérifie un corps vérifié et renvoie KeenAuthEvent | UnknownEvent. Après isKnownEvent, switch (event.type) restreint l'événement à un type précis. verifyEvent dans @keenauth/node/webhooks effectue ces deux étapes :

import { verifyEvent } from "@keenauth/node/webhooks";
import { isKnownEvent } from "@keenauth/node/events";

const event = verifyEvent(webhook, req.body, req.headers); // WebhookVerificationError ou EventParseError
if (!isKnownEvent(event)) {
  // Un type que cette version du SDK ne connaît pas encore. event.type et event.data.object.id restent typés.
  return res.sendStatus(204);
}
switch (event.type) {
  case "user.created":
    console.log(event.data.object.id);
    break;
  case "email_sending.suspended":
    console.log(event.data.object.reason); // "bounce_rate" | "complaint_rate" | (string & {})
    break;
}

@keenauth/node/claims type les revendications d'un jeton vérifié : parseIdTokenClaims (OpenID Connect Core) et parseAccessTokenClaims (RFC 9068). Elles contrôlent les types des revendications mais ne vérifient rien ; appelez-les donc sur les claims renvoyés par le vérificateur :

import { parseAccessTokenClaims } from "@keenauth/node/claims";

const at = await verifier.verifyAccessToken(token);
const claims = parseAccessTokenClaims(at.claims);
claims.client_id; // string
claims.org_roles; // string[] | undefined
claims["tenant_tier"]; // unknown : vos propres revendications

Une revendication non conforme lève une ClaimsParseError avec son nom.

Avertissements de dépréciation

Lorsqu'une réponse contient un en-tête Deprecation (RFC 9745) ou Sunset (RFC 8594), le SDK écrit un avertissement par point de terminaison et par valeur d'en-tête pendant toute la durée du processus. L'avertissement indique la méthode, le chemin dont les identifiants sont remplacés par {id}, ainsi que les valeurs des en-têtes ; il ne contient jamais de jetons, de chaînes de requête ni de données utilisateur. Il ne lève jamais d'exception.

Par défaut, l'avertissement est transmis à process.emitWarning avec le type KeenAuthDeprecationWarning. Pour l'envoyer à votre journal à la place :

const keenauth = new KeenAuth({
  baseUrl: "https://api.eu.keenauth.com/v1",
  apiKey: process.env.KEENAUTH_API_KEY!,
  onWarning: (message) => logger.warn(message),
});

En-tête de télémétrie

Chaque requête contient Idp-Client: keenauth-ts/<version> (node/<version>). Cet en-tête ne contient aucune donnée personnelle et ne peut pas être désactivé ; KeenAuth l'utilise pour l'assistance et pour vous informer des versions du SDK devenues obsolètes.