Node.js-Verwaltungs-SDK

@keenauth/node ist das TypeScript-SDK für die Verwaltungs-API von KeenAuth. Es benötigt Node.js 22 oder neuer und hat keine Laufzeitabhängigkeiten. Es ist noch nicht in npm veröffentlicht.

Das Paket bietet folgende Einstiegspunkte:

Import Verwendung für
@keenauth/node Den Client der Verwaltungs-API, Verwaltungs-Tokens, Paginierung und Fehler
@keenauth/node/webhooks Überprüfung von Webhook-Signaturen, ohne den API-Client zu laden
@keenauth/node/bff Anmeldung für Server-Anwendungen: Express und Fastify
@keenauth/node/middleware Schutz Ihrer API mit Bearer-Tokens
@keenauth/node/testing Das Offline-Testkit

Client erstellen

Mit einem geheimen oder eingeschränkten API-Schlüssel:

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 ist die API-Adresse Ihrer Region mit /v1. Sie muss https verwenden; einfaches http ist nur für localhost zulässig. Der Schlüssel wird ausschließlich im Authorization-Header und nur an diese Adresse gesendet. Weiterleitungen werden nicht verfolgt.

Ohne API-Schlüssel authentifizieren

Eine management-Anwendung kann die API mit einem kurzlebigen Token statt mit einem statischen Schlüssel aufrufen. Die Anwendung signiert eine Client-Assertion mit ihrem privaten Schlüssel (private_key_jwt, RFC 7523), und das SDK tauscht sie am Token-Endpunkt Ihrer Umgebung gegen ein Token ein.

  1. Erstellen Sie im Dashboard eine Anwendung vom Typ management und einen Verwaltungsclient-Eintrag mit den benötigten Berechtigungen (siehe Verwaltungsclient erstellen).
  2. Erzeugen Sie ein Schlüsselpaar und registrieren Sie den öffentlichen Schlüssel als public_key-Credential der Anwendung. Für ES256 zum Beispiel: openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out management-key.pem.
  3. Bewahren Sie den privaten Schlüssel in Ihrem Secret-Store auf. Übergeben Sie ihn an das 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", // your environment's issuer, exactly
    clientId: "app_01j9zzzzzzzzzzzzzzzzzzzz",
    privateKey: readFileSync("management-key.pem", "utf8"), // PEM, a private JWK or a KeyObject
    scope: ["users:read", "users:write"], // optional; default: the application's default scopes
  },
});

Das SDK arbeitet dabei wie folgt:

Schlägt eine Token-Anfrage fehl, wirft das SDK TokenError mit dem OAuth-code (zum Beispiel invalid_client oder invalid_scope), description und status. Ein Konfigurationsfehler, etwa ein öffentlicher statt eines privaten Schlüssels, wird beim Erstellen des Clients ausgelöst.

apiKey und credentials können nicht gleichzeitig verwendet werden.

Wiederholungen

Das SDK wiederholt eine Anfrage insgesamt höchstens 3-mal, wenn die API mit 429, 502, 503 oder 504 antwortet oder wenn die Verbindung fehlschlägt. Wiederholt werden nur GET-, PUT- und DELETE-Anfragen sowie POST-Anfragen mit einem Idempotency-Key. Ein PATCH mit If-Match wird nur bei 429 wiederholt: Der Rate-Limiter lehnt die Anfrage ab, bevor sie ausgeführt wird, und If-Match verhindert, dass eine Änderung doppelt angewendet wird. Jeder andere PATCH wird nie wiederholt.

Das SDK fügt jedem POST mit einer Operation, die einen Idempotency-Key akzeptiert, einen zufälligen Schlüssel hinzu, damit ein wiederholtes Anlegen kein zweites Objekt erzeugt. Damit auch Ihre eigenen Wiederholungen sicher sind, übergeben Sie denselben Schlüssel selbst:

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

Paginierung

Listen-Endpunkte liefern eine Seite zurück: data und listMetadata mit den Cursorn before und after. paginate folgt den Cursorn für Sie:

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; // stops without loading the next page
}

Fehler

Eine Fehlerantwort wird zu einem ProblemError (RFC 9457) mit status, code, title, detail, requestId und den Feldfehlern errors. Prüfen Sie code, nicht die Meldung. Codes, die das SDK nicht kennt, bleiben als Zeichenketten verfügbar.

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

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

Eine Antwort ohne Problem-Dokument, etwa eine HTML-Seite eines Proxys, wird zum generierten ResponseError mit der rohen response.

Webhooks überprüfen

KeenAuth signiert Webhooks nach dem Standard Webhooks v1-Verfahren. Überprüfen Sie die Signatur mit dem rohen Anfragekörper, bevor Sie ihn parsen:

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;
  }
  // handle event.type, then answer quickly
  res.sendStatus(204);
});

Webhooks können mehr als einmal eintreffen. Verwenden Sie webhook-id, um Duplikate zu ignorieren.

Typisierte Ereignisse und Claims

@keenauth/node/events enthält einen Typ für jedes Kundenereignis, generiert aus den öffentlichen Ereignisschemata. parseEvent prüft einen verifizierten Körper und gibt KeenAuthEvent | UnknownEvent zurück. Nach isKnownEvent grenzt switch (event.type) das Ereignis auf einen Typ ein. verifyEvent in @keenauth/node/webhooks führt beide Schritte aus:

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

const event = verifyEvent(webhook, req.body, req.headers); // WebhookVerificationError or EventParseError
if (!isKnownEvent(event)) {
  // A type this SDK version does not know yet. event.type and event.data.object.id are still typed.
  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 typisiert die Claims eines verifizierten Tokens: parseIdTokenClaims (OpenID Connect Core) und parseAccessTokenClaims (RFC 9068). Sie prüfen Claim-Typen, verifizieren aber nichts; rufen Sie sie daher mit den claims auf, die der Verifier zurückgibt:

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: your own claims

Ein nicht passender Claim wirft ClaimsParseError mit seinem Namen.

Warnungen zu veralteten Funktionen

Trägt eine Antwort einen Deprecation-Header (RFC 9745) oder Sunset-Header (RFC 8594), schreibt das SDK eine Warnung pro Endpunkt und Header-Wert für die Lebensdauer des Prozesses. Die Warnung nennt die Methode, den Pfad mit durch {id} ersetzten IDs und die Header-Werte; sie enthält nie Tokens, Query-Strings oder Nutzerdaten. Sie löst nie einen Fehler aus.

Standardmäßig geht die Warnung an process.emitWarning mit dem Typ KeenAuthDeprecationWarning. Um sie stattdessen an Ihren Logger zu senden:

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

Telemetrie-Header

Jede Anfrage trägt Idp-Client: keenauth-ts/<version> (node/<version>). Der Header enthält keine personenbezogenen Daten und kann nicht deaktiviert werden; KeenAuth verwendet ihn für den Support und um Sie über veraltete SDK-Versionen zu informieren.