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

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.

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.

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