SDK di gestione per Node.js
@keenauth/node è l'SDK TypeScript per la management API di KeenAuth. Richiede Node.js 22 o successivo e non ha dipendenze di runtime. Non è ancora pubblicato su npm.
Il pacchetto ha questi entry point:
| Import | Usalo per |
|---|---|
@keenauth/node |
Il client della management API, i token di gestione, la paginazione e gli errori |
@keenauth/node/webhooks |
Verificare le firme dei webhook, senza caricare il client API |
@keenauth/node/bff |
Accesso per le app server: Express e Fastify |
@keenauth/node/middleware |
Proteggere la tua API con bearer token |
@keenauth/node/testing |
Il kit di test offline |
Crea un client
Con una chiave API secret o restricted:
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 è l'indirizzo API della tua regione con /v1. Deve usare https; http semplice è accettato solo per localhost. La chiave viene inviata solo nell'header Authorization e solo a quell'indirizzo. I redirect non vengono seguiti.
Autenticati senza una chiave API
Un'applicazione di tipo management può chiamare l'API con un token a breve durata invece di una chiave statica. L'applicazione firma un client assertion con la propria chiave privata (private_key_jwt, RFC 7523), e l'SDK lo scambia all'endpoint dei token del tuo ambiente.
- Nella dashboard, crea un'applicazione di tipo
managemente un record di client di gestione con le autorizzazioni di cui ha bisogno (vedi Crea un client di gestione). - Genera una coppia di chiavi e registra la chiave pubblica come credenziale
public_keydell'applicazione. Per esempio, per ES256:openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out management-key.pem. - Conserva la chiave privata nel tuo secret store. Passala all'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
},
});
Cosa fa l'SDK:
- Invia
grant_type=client_credentialsa<issuer>/oauth2/tokenconresourceimpostato all'identificatore della management API. Il valore predefinito è l'origine dibaseUrl, per esempiohttps://api.eu.keenauth.com; impostacredentials.audienceper cambiarlo. - Ogni richiesta riceve un nuovo client assertion:
issesubsono il client ID,audè l'issuer,jtiè casuale e l'assertion è valida per 120 secondi. L'headerkidè il thumbprint RFC 7638 della chiave, con cui KeenAuth trova la chiave quando l'applicazione ne ha più d'una (per la rotazione delle chiavi). - L'algoritmo segue la chiave: le chiavi RSA firmano con
RS256(impostaalg: "PS256"per RSA-PSS), le chiavi P-256 conES256e le chiavi Ed25519 conEdDSA. Le chiavi RSA devono avere almeno 2048 bit. - Il token di accesso viene messo in cache e rinnovato 60 secondi prima della scadenza. Le chiamate concorrenti condividono un solo rinnovo. Se l'API risponde
401, il token in cache viene scartato e la chiamata successiva ne ottiene uno nuovo.
Una richiesta di token non riuscita genera TokenError con il code OAuth (per esempio invalid_client o invalid_scope), description e status. Un errore di configurazione, come una chiave pubblica al posto di una privata, viene generato al momento della creazione del client.
apiKey e credentials non possono essere usati insieme.
Retry
L'SDK ritenta una richiesta al massimo 3 volte in totale quando l'API risponde 429, 502, 503 o 504, oppure quando la connessione fallisce. Ritenta solo richieste GET, PUT e DELETE e richieste POST che includono un Idempotency-Key. Una PATCH che include If-Match viene ritentata solo su 429: il rate limiter rifiuta la richiesta prima che venga eseguita, e If-Match evita che una modifica venga applicata due volte. Qualsiasi altra PATCH non viene mai ritentata.
- Se la risposta ha un header
Retry-After(secondi o data HTTP), l'SDK attende quel tempo. UnRetry-Aftersuperiore a 60 secondi non viene atteso: l'SDK restituisce a te quella risposta. - Altrimenti attende 0,5 secondi, poi 1 secondo, ciascuno con jitter casuale di ±25%.
timeoutMs(predefinito 30 secondi) limita l'intera chiamata, retry inclusi. Se un'attesa andrebbe oltre, l'SDK restituisce invece l'ultima risposta.
L'SDK aggiunge un Idempotency-Key casuale a ogni POST la cui operazione lo accetta, così una creazione ritentata non crea un secondo oggetto. Per rendere sicuri anche i tuoi retry, passa tu la stessa chiave:
await keenauth.users.createUser({
userCreateRequest: { email: "ada@example.com" },
idempotencyKey: "signup-7d1f0c2a-0d7e-4c55",
});
Paginazione
Gli endpoint di lista restituiscono una pagina: data e listMetadata con i cursori before e after. paginate segue i cursori al posto tuo:
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
}
- Senza
before, avanza conafterfinché il cursore ènull. Conbefore, procede all'indietro. limitè la dimensione della pagina (1-100) ed è inviato in ogni richiesta; anche gli altri parametri vengono mantenuti.paginatePagesrestituisce intere pagine invece dei singoli elementi.- Se il server restituisce due volte lo stesso cursore, il ciclo si interrompe con
PaginationError.
Errori
Una risposta di errore diventa un ProblemError (RFC 9457) con status, code, title, detail, requestId e gli errors dei singoli campi. Controlla code, non il messaggio. I codici che l'SDK non conosce restano disponibili come stringhe.
import { ProblemError } from "@keenauth/node";
try {
await keenauth.users.getUser({ userId });
} catch (e) {
if (e instanceof ProblemError && e.code === "not_found") {
// …
}
}
Una risposta senza documento di problema, come una pagina HTML di un proxy, diventa il ResponseError generato con la response grezza.
Verifica i webhook
KeenAuth firma i webhook con lo schema Standard Webhooks v1. Verifica la firma con il corpo grezzo della richiesta, prima di analizzarlo:
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);
});
- L'SDK controlla gli header
webhook-id,webhook-timestampewebhook-signature. Il timestamp deve trovarsi entro 5 minuti dall'orologio del tuo server, in entrambe le direzioni. - Durante la rotazione di un secret, passa entrambi:
new Webhook([newSecret, oldSecret]). Una richiesta viene accettata quando una qualsiasi firmav1corrisponde a un qualsiasi secret. - Le firme vengono confrontate in tempo costante.
e.kindindica cosa non ha funzionato:headers(manca un header),timestamp(non è un numero o è fuori dalla tolleranza),signature(nessuna firma corrisponde) osecret(il secret configurato non è un valoreidp_whsec_valido, errore di configurazione generato danew Webhook). Ogni tipo ha anche una propria classe, comeWebhookSignatureError.verifyrestituisce il corpo JSON analizzato.verifyRawrestituisce i byte verificati.
I webhook possono arrivare più di una volta. Usa webhook-id per ignorare i duplicati.
Eventi tipizzati e claim
@keenauth/node/events ha un tipo per ogni evento cliente, generato dagli schemi pubblici degli eventi. parseEvent controlla un corpo verificato e restituisce KeenAuthEvent | UnknownEvent. Dopo isKnownEvent, switch (event.type) restringe l'evento a un tipo specifico. verifyEvent in @keenauth/node/webhooks esegue entrambi i passaggi:
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;
}
parseEventaccetta testo JSON, byte o un valore già decodificato. Controlla l'envelope e i membri dichiarati dallo schema del tipo. Un membro mancante o di tipo errato generaEventParseErrorcon il suo percorso, comedata.object.reason.- Un tipo sconosciuto, o un tipo noto con uno
schema_versionche questo SDK non modella, è unUnknownEvent. Non è un errore. - I membri non dichiarati dallo schema restano sull'oggetto.
- I campi enumerati sono tipizzati come i valori noti più
string, quindi un nuovo valore inviato dal server passa comunque il controllo di tipo e viene analizzato.
@keenauth/node/claims tipizza i claim di un token verificato: parseIdTokenClaims (OpenID Connect Core) e parseAccessTokenClaims (RFC 9068). Controllano i tipi dei claim ma non verificano nulla, quindi chiamali sui claims restituiti dal verificatore:
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
Un claim che non corrisponde genera ClaimsParseError con il suo nome.
Avvisi di deprecazione
Quando una risposta contiene un header Deprecation (RFC 9745) o Sunset (RFC 8594), l'SDK scrive un avviso per ogni endpoint e valore dell'header per tutta la vita del processo. L'avviso indica il metodo, il percorso con gli ID sostituiti da {id} e i valori dell'header; non contiene mai token, query string o dati utente. Non genera mai eccezioni.
Per impostazione predefinita l'avviso va a process.emitWarning con il tipo KeenAuthDeprecationWarning. Per inviarlo al tuo logger:
const keenauth = new KeenAuth({
baseUrl: "https://api.eu.keenauth.com/v1",
apiKey: process.env.KEENAUTH_API_KEY!,
onWarning: (message) => logger.warn(message),
});
Header di telemetria
Ogni richiesta contiene Idp-Client: keenauth-ts/<version> (node/<version>). Non contiene dati personali e non può essere disattivato; KeenAuth lo usa per il supporto e per avvisarti delle versioni dell'SDK deprecate.