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.
- Erstellen Sie im Dashboard eine Anwendung vom Typ
managementund einen Verwaltungsclient-Eintrag mit den benötigten Berechtigungen (siehe Verwaltungsclient erstellen). - 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. - 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:
- Es sendet
grant_type=client_credentialsan<issuer>/oauth2/token, wobeiresourceauf den Bezeichner der Verwaltungs-API gesetzt ist. Standard ist der Ursprung vonbaseUrl, zum Beispielhttps://api.eu.keenauth.com; setzen Siecredentials.audience, um ihn zu ändern. - Jede Anfrage erhält eine neue Client-Assertion:
issundsubsind die Client-ID,audist der Issuer,jtiist zufällig und die Assertion ist 120 Sekunden gültig. Derkid-Header ist der RFC-7638-Thumbprint des Schlüssels; damit findet KeenAuth den Schlüssel, wenn die Anwendung mehrere besitzt (etwa bei einer Schlüsselrotation). - Der Algorithmus richtet sich nach dem Schlüssel: RSA-Schlüssel signieren mit
RS256(setzen Siealg: "PS256"für RSA-PSS), P-256-Schlüssel mitES256und Ed25519-Schlüssel mitEdDSA. RSA-Schlüssel müssen mindestens 2048 Bit haben. - Das Zugriffstoken wird zwischengespeichert und 60 Sekunden vor Ablauf erneuert. Gleichzeitige Aufrufe teilen sich eine Erneuerung. Antwortet die API mit
401, wird das zwischengespeicherte Token verworfen und der nächste Aufruf erhält ein neues.
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.
- Enthält die Antwort einen
Retry-After-Header (Sekunden oder HTTP-Datum), wartet das SDK so lange. EinRetry-Aftervon mehr als 60 Sekunden wird nicht abgewartet: Das SDK gibt diese Antwort an Sie zurück. - Andernfalls wartet es 0,5 Sekunden, dann 1 Sekunde, jeweils mit einer zufälligen Abweichung von ±25 %.
timeoutMs(Standard 30 Sekunden) begrenzt den gesamten Aufruf einschließlich Wiederholungen. Würde eine Wartezeit darüber hinausgehen, gibt das SDK stattdessen die letzte Antwort zurück.
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
}
- Ohne
beforeläuft es mitaftervorwärts, bis der Cursornullist. Mitbeforeläuft es rückwärts. limitist die Seitengröße (1–100) und wird bei jeder Anfrage gesendet; die übrigen Parameter bleiben ebenfalls erhalten.paginatePagesliefert ganze Seiten statt einzelner Elemente.- Liefert der Server denselben Cursor zweimal, beendet
PaginationErrordie Schleife.
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);
});
- Das SDK prüft die Header
webhook-id,webhook-timestampundwebhook-signature. Der Zeitstempel muss innerhalb von 5 Minuten zur Uhr Ihres Servers liegen, in beide Richtungen. - Während einer Geheimnisrotation übergeben Sie beide Geheimnisse:
new Webhook([newSecret, oldSecret]). Eine Anfrage wird akzeptiert, wenn einev1-Signatur zu einem beliebigen Geheimnis passt. - Signaturen werden in konstanter Zeit verglichen.
e.kindzeigt an, was fehlgeschlagen ist:headers(ein Header fehlt),timestamp(keine Zahl oder außerhalb der Toleranz),signature(keine Signatur passt) odersecret(das konfigurierte Geheimnis ist kein gültigeridp_whsec_-Wert; ein Konfigurationsfehler, der vonnew Webhookausgelöst wird). Jede Art hat außerdem eine eigene Klasse, etwaWebhookSignatureError.verifygibt den geparsten JSON-Körper zurück.verifyRawgibt die verifizierten Bytes zurück.
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;
}
parseEventakzeptiert JSON-Text, Bytes oder einen bereits dekodierten Wert. Es prüft die Hülle und die Felder, die das Schema des Typs deklariert. Ein fehlendes oder falsch typisiertes Feld wirftEventParseErrormit seinem Pfad, etwadata.object.reason.- Ein unbekannter Typ oder ein bekannter Typ mit einer
schema_version, die dieses SDK nicht modelliert, ist einUnknownEvent. Das ist kein Fehler. - Felder, die das Schema nicht deklariert, bleiben am Objekt erhalten.
- Aufzählungsfelder sind als die bekannten Werte plus
stringtypisiert, sodass ein neuer Wert vom Server weiterhin typgeprüft und geparst wird.
@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.