Test offline con il test kit
Ogni SDK server (Go, TypeScript e Python) ha un modulo testing per unit test e CI. Emette access token e ID token firmati con una chiave di test fissa, costruisce un verificatore di token che accetta esattamente quei token senza alcun accesso alla rete, fornisce payload di evento di esempio e firma le consegne dei webhook con un segreto di test pubblico. I tuoi test girano senza un ambiente KeenAuth, senza credenziali e senza internet.
Come funziona
- Una chiave di test fissa e pubblica. I token di test sono firmati con ES256 e con l'ID di chiave
keenauth-test-1. La chiave è volutamente pubblica: la sua parte privata deriva da un'etichetta pubblicata, e il set di chiavi pubbliche è incluso in ogni SDK. Non fidarti mai di questa chiave in nessun altro contesto. - Un issuer che non può esistere. I token di test portano l'issuer
https://test.keenauth.invalid. Il dominio di primo livello.invalidè riservato (RFC 2606) e non si risolve mai, quindi nessun ambiente reale può avere questo issuer. - Modalità di test esplicita. Il verificatore dell'SDK accetta questo issuer solo quando la modalità di test è attivata nel codice. Un verificatore configurato in modo normale rifiuta qualsiasi issuer sotto
.invalidcome errore di configurazione, quindi un verificatore di produzione non potrà mai accettare un token di test, anche se il suo issuer fosse stato impostato per errore sull'issuer di test. Anche un token firmato con la chiave di test ma con il tuo issuer reale viene rifiutato, perché il tuo ambiente pubblica chiavi diverse. - Nessuna rete. In modalità di test il verificatore prende la chiave di test dalla memoria e non scarica mai nulla. Accetta solo l'issuer di test.
La modalità di test non indebolisce gli altri controlli: i token scaduti, un audience errato, un payload modificato, un jti mancante e un token vincolato con DPoP senza una prova vengono rifiutati esattamente come in produzione. Per l'elenco completo vedi Verifica dei token nella tua API.
Tieni la modalità di test solo nel codice di test. Chiunque può firmare token di test, quindi un servizio che l'abilitasse accetterebbe token contraffatti.
Go
Il pacchetto è keenauthtest, sullo stile di net/http/httptest.
import (
"github.com/keenauth/keenauth-go/keenauthtest"
"github.com/keenauth/keenauth-go/verify"
)
func TestOrdersAPI(t *testing.T) {
v, err := keenauthtest.NewVerifier(verify.Config{Audience: "https://api.acme.example"})
if err != nil {
t.Fatal(err)
}
token, err := keenauthtest.AccessToken("https://api.acme.example",
keenauthtest.WithClaims(map[string]any{"scope": "orders:read", "org_id": "org_01m3vnbwg0fvav6zw6w7v3st0r"}))
if err != nil {
t.Fatal(err)
}
// Passa v all'handler sotto test e invia "Authorization: Bearer " + token.
claims, err := v.VerifyAccessToken(context.Background(), token, nil)
if err != nil || claims.Scope != "orders:read" {
t.Fatal(err)
}
}
keenauthtest.IDToken(clientID, ...) emette un ID token. WithLifetime cambia la durata (un valore negativo genera un token scaduto) e WithIssuedAt imposta la data di emissione. In WithClaims un valore nil rimuove un claim. Senza il pacchetto puoi impostare tu stesso verify.Config{TestingMode: true}.
TypeScript
Il modulo è il sottopercorso @keenauth/node/testing.
import { createTestVerifier, mintAccessToken } from "@keenauth/node/testing";
const verifier = createTestVerifier({ audience: "https://api.acme.example" });
const token = mintAccessToken("https://api.acme.example", {
claims: { scope: "orders:read", org_id: "org_01m3vnbwg0fvav6zw6w7v3st0r" },
});
const claims = await verifier.verifyAccessToken(token);
// claims.subject, claims.scope, claims.claims.org_id
mintIdToken(clientId, ...) emette un ID token. Le opzioni sono claims (un valore null rimuove un claim), lifetimeSeconds e issuedAt. Senza il modulo puoi passare testing: true a createVerifier.
Python
Il modulo è keenauth.testing; richiede l'extra verify, come il verificatore.
from keenauth import testing
verifier = testing.verifier(audience="https://api.acme.example")
token = testing.mint_access_token(
"https://api.acme.example",
claims={"scope": "orders:read", "org_id": "org_01m3vnbwg0fvav6zw6w7v3st0r"},
)
claims = verifier.verify_access_token(token)
# claims.subject, claims.scope, claims.claims["org_id"]
testing.mint_id_token(client_id, ...) emette un ID token. Gli argomenti con nome sono claims (un valore None rimuove un claim), lifetime_seconds e issued_at. Senza il modulo puoi passare testing=True a VerifierConfig.
Claim predefiniti
| Claim | Access token | ID token |
|---|---|---|
iss |
https://test.keenauth.invalid |
https://test.keenauth.invalid |
sub |
usr_01m3vnbwg0e8mbe7h7xxp6rmkc |
usr_01m3vnbwg0e8mbe7h7xxp6rmkc |
aud |
l'audience che passi | il client ID che passi |
client_id |
app_01m3vnbwg0fqba5ntanfd63s8n |
— |
iat, exp |
ora, ora + 3600 secondi | ora, ora + 3600 secondi |
jti |
casuale | — |
auth_time |
— | uguale a iat |
L'header dell'access token ha typ: at+jwt (RFC 9068). Aggiungi i claim che la tua API legge, come scope, org_id, permissions, acr, amr o auth_time. Per gli ID token, aggiungi nonce quando il tuo codice lo controlla.
Eventi di esempio
Il kit include payload di esempio dei principali eventi: user.created, user.updated, user.deleted, session.created, session.revoked, organization.created e organization_membership.created. Usano la stessa envelope e gli stessi schemi degli eventi che KeenAuth invia, nella modalità thin predefinita: identificatori, stato e nomi dei campi modificati, nessun dato personale. Usali per testare i tuoi gestori di eventi.
- Go:
keenauthtest.Event("user.created")restituisce il JSON;keenauthtest.EventTypes()elenca i tipi. - TypeScript:
sampleEvent("user.created")esampleEventTypes(). - Python:
testing.sample_event("user.created")etesting.event_types().
Test dei webhook
Il kit firma un payload esattamente come una consegna di webhook di KeenAuth (Standard Webhooks v1): restituisce il body grezzo e gli header webhook-id, webhook-timestamp e webhook-signature. Il tuo handler lo verifica con il verificatore webhook dell'SDK, lo stesso codice che gira in produzione. Cambia solo il segreto.
- Un segreto di test pubblico. Per impostazione predefinita il kit firma con un segreto di test fisso
idp_whsec_la cui chiave deriva dall'etichetta pubblicataKeenAuth offline test kit webhook secret (public, TEST ONLY). Chiunque può calcolarlo, quindi non configurarlo mai su un endpoint reale. Configura il tuo verificatore con esso nei test, oppure passa un tuo segreto all'helper. - Uguale a una consegna reale.
webhook-idè l'iddell'evento (o un IDmsg_casuale quando il payload non ne ha uno) ewebhook-timestampè l'ora attuale, salvo che tu li imposti. Il body viene inviato esattamente come lo passi. - Replay e manomissione. Firma con un timestamp vecchio per verificare che il tuo handler rifiuti le consegne fuori dalla finestra di 5 minuti, e modifica il body dopo la firma per verificare che rifiuti una firma non valida.
Node
import { Webhook, WebhookVerificationError } from "@keenauth/node/webhooks";
import { sampleEvent, signWebhook, testWebhookSecret } from "@keenauth/node/testing";
const webhook = new Webhook(testWebhookSecret()); // in produzione: process.env.KEENAUTH_WEBHOOK_SECRET
const delivery = signWebhook(sampleEvent("user.created"));
const event = webhook.verify(delivery.body, delivery.headers); // l'evento analizzato
// Una consegna di un'ora fa viene rifiutata.
const old = signWebhook(sampleEvent("user.created"), { timestamp: Math.floor(Date.now() / 1000) - 3600 });
try {
webhook.verify(old.body, old.headers);
} catch (e) {
// e instanceof WebhookVerificationError, e.kind === "timestamp"
}
signWebhook(payload, options) accetta una stringa, un Uint8Array o un oggetto (serializzato con JSON.stringify). Le opzioni sono secret, id e timestamp (secondi Unix o un Date). Per testare un handler HTTP, invia delivery.body come body grezzo della richiesta con gli header di delivery.headers e Content-Type: application/json.
Python
import time
from keenauth import testing
from keenauth.webhooks import WebhookTimestampError, WebhookVerifier
verifier = WebhookVerifier(testing.webhook_secret()) # in produzione: os.environ["KEENAUTH_WEBHOOK_SECRET"]
delivery = testing.sign_webhook(testing.sample_event("user.created"))
verifier.verify(delivery.body, delivery.headers)
# Una consegna di un'ora fa viene rifiutata.
old = testing.sign_webhook(testing.sample_event("user.created"), timestamp=int(time.time()) - 3600)
try:
verifier.verify(old.body, old.headers)
except WebhookTimestampError:
pass
testing.sign_webhook(payload, secret=None, msg_id=None, timestamp=None) accetta bytes, una stringa o un dizionario (serializzato come JSON compatto) e restituisce delivery.body (bytes) e delivery.headers (un dizionario). Con un client di test come quelli di Flask o FastAPI, invia delivery.body come body grezzo con delivery.headers.
Go
import (
"net/http"
"net/http/httptest"
"github.com/keenauth/keenauth-go/keenauthtest"
"github.com/keenauth/keenauth-go/webhook"
)
func TestWebhookHandler(t *testing.T) {
v, err := webhook.NewVerifier([]string{keenauthtest.WebhookSecret()}) // in produzione: os.Getenv("KEENAUTH_WEBHOOK_SECRET")
if err != nil {
t.Fatal(err)
}
body, err := keenauthtest.Event("user.created")
if err != nil {
t.Fatal(err)
}
delivery, err := keenauthtest.SignWebhook(body)
if err != nil {
t.Fatal(err)
}
req, err := delivery.Request("http://localhost/webhooks/keenauth")
if err != nil {
t.Fatal(err)
}
rec := httptest.NewRecorder()
newWebhookHandler(v).ServeHTTP(rec, req) // il tuo handler, che chiama v.Verify(r.Header, body)
if rec.Code != http.StatusOK {
t.Fatalf("status %d", rec.Code)
}
}
keenauthtest.SignWebhook(payload, ...) restituisce delivery.Body e delivery.Header; delivery.Request(url) costruisce la richiesta POST con Content-Type: application/json. Le opzioni sono WithWebhookSecret, WithWebhookID e WithWebhookTimestamp.
Cosa non fa il kit
- Non emula KeenAuth. I flussi di accesso, le regole di emissione dei token, i rate limit e ogni altro comportamento del server sono reali solo nel tuo ambiente ospitato
development; esegui lì i test di integrazione prima di rilasciare. - Non esiste un orologio di test. Per controllare il tempo, passa al verificatore un tuo orologio (
Now,now) e imposta la data di emissione del token. - Non crea prove DPoP. Puoi aggiungere un claim
cnf.jkt, ma in quel caso devi firmare la prova tu stesso. - Non consegna webhook. Le consegne reali, i tentativi e la selezione degli eventi sono reali solo nel tuo ambiente ospitato
development.