Tester hors ligne avec le kit de test
Chaque SDK serveur (Go, TypeScript et Python) dispose d'un module testing pour les tests unitaires et la CI. Il génère des jetons d'accès et des jetons ID signés avec une clé de test fixe, crée un vérificateur de jetons qui n'accepte que ces jetons sans aucun accès réseau, fournit des exemples de charges utiles d'événements et signe les livraisons de webhooks avec un secret de test public. Vos tests s'exécutent sans environnement KeenAuth, sans identifiants et sans accès à Internet.
Fonctionnement
- Une clé de test fixe et publique. Les jetons de test sont signés avec ES256 et l'identifiant de clé
keenauth-test-1. La clé est volontairement publique : sa partie privée est dérivée d'un libellé publié, et l'ensemble des clés publiques figure dans chaque SDK. Ne faites jamais confiance à cette clé ailleurs. - Un émetteur qui ne peut pas exister. Les jetons de test portent l'émetteur
https://test.keenauth.invalid. Le domaine de premier niveau.invalidest réservé (RFC 2606) et ne se résout jamais ; aucun environnement réel ne peut donc avoir cet émetteur. - Un mode de test explicite. Le vérificateur du SDK n'accepte cet émetteur que si le mode de test est activé dans le code. Un vérificateur configuré de la manière habituelle refuse tout émetteur sous
.invalidcomme erreur de configuration : un vérificateur de production ne peut donc jamais accepter un jeton de test, même si son émetteur a été réglé par erreur sur l'émetteur de test. Un jeton signé avec la clé de test mais portant votre émetteur réel est également rejeté, car votre environnement publie d'autres clés. - Aucun accès réseau. En mode de test, le vérificateur charge la clé de test depuis la mémoire et ne récupère jamais rien. Il n'accepte que l'émetteur de test.
Le mode de test n'affaiblit pas les autres contrôles : les jetons expirés, une audience incorrecte, une charge utile modifiée, un jti manquant et un jeton lié à DPoP sans preuve sont rejetés exactement comme en production. Consultez Vérifier les jetons dans votre API pour la liste complète.
Réservez le mode de test au code de test. N'importe qui peut signer des jetons de test : un service qui l'activerait accepterait donc des jetons falsifiés.
Go
Le package est keenauthtest, sur le modèle de 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)
}
// Passez v au gestionnaire testé et envoyez "Authorization: Bearer " + token.
claims, err := v.VerifyAccessToken(context.Background(), token, nil)
if err != nil || claims.Scope != "orders:read" {
t.Fatal(err)
}
}
keenauthtest.IDToken(clientID, ...) génère un jeton ID. WithLifetime modifie la durée de vie (une valeur négative donne un jeton expiré) et WithIssuedAt définit la date d'émission. Dans WithClaims, une valeur nil supprime une revendication. Sans le package, vous pouvez définir vous-même verify.Config{TestingMode: true}.
TypeScript
Le module est le sous-chemin @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, ...) génère un jeton ID. Les options sont claims (une valeur null supprime une revendication), lifetimeSeconds et issuedAt. Sans le module, vous pouvez passer testing: true à createVerifier.
Python
Le module est keenauth.testing ; il nécessite l'extra verify, comme le vérificateur.
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, ...) génère un jeton ID. Les arguments nommés sont claims (une valeur None supprime une revendication), lifetime_seconds et issued_at. Sans le module, vous pouvez passer testing=True à VerifierConfig.
Revendications par défaut
| Revendication | Jeton d'accès | Jeton ID |
|---|---|---|
iss |
https://test.keenauth.invalid |
https://test.keenauth.invalid |
sub |
usr_01m3vnbwg0e8mbe7h7xxp6rmkc |
usr_01m3vnbwg0e8mbe7h7xxp6rmkc |
aud |
l'audience que vous passez | l'identifiant client que vous passez |
client_id |
app_01m3vnbwg0fqba5ntanfd63s8n |
— |
iat, exp |
maintenant, maintenant + 3600 secondes | maintenant, maintenant + 3600 secondes |
jti |
aléatoire | — |
auth_time |
— | identique à iat |
L'en-tête du jeton d'accès contient typ: at+jwt (RFC 9068). Ajoutez les revendications que lit votre API, comme scope, org_id, permissions, acr, amr ou auth_time. Pour les jetons ID, ajoutez nonce si votre code le vérifie.
Exemples d'événements
Le kit comprend des exemples de charges utiles pour les événements courants : user.created, user.updated, user.deleted, session.created, session.revoked, organization.created et organization_membership.created. Ils utilisent la même enveloppe et les mêmes schémas que les événements envoyés par KeenAuth, en mode allégé par défaut : identifiants, état et noms des champs modifiés, sans données personnelles. Utilisez-les pour tester vos gestionnaires d'événements.
- Go :
keenauthtest.Event("user.created")renvoie le JSON ;keenauthtest.EventTypes()liste les types. - TypeScript :
sampleEvent("user.created")etsampleEventTypes(). - Python :
testing.sample_event("user.created")ettesting.event_types().
Tester les webhooks
Le kit signe une charge utile exactement comme une livraison de webhook KeenAuth (Standard Webhooks v1) : il renvoie le corps brut ainsi que les en-têtes webhook-id, webhook-timestamp et webhook-signature. Votre gestionnaire la vérifie avec le vérificateur de webhooks du SDK, c'est-à-dire le même code qu'en production. Seul le secret diffère.
- Un secret de test public. Par défaut, le kit signe avec un secret de test
idp_whsec_fixe, dont la clé est dérivée du libellé publiéKeenAuth offline test kit webhook secret (public, TEST ONLY). N'importe qui peut le calculer : ne le configurez jamais sur un point de terminaison réel. Configurez votre vérificateur avec ce secret dans vos tests, ou transmettez votre propre secret à l'assistant. - Identique à une livraison réelle.
webhook-idcorrespond à l'idde l'événement (ou à un identifiantmsg_aléatoire si la charge utile n'en a pas) etwebhook-timestampcorrespond à l'instant présent, sauf si vous les définissez. Le corps est envoyé exactement tel que vous le passez. - Rejeu et altération. Signez avec un horodatage ancien pour vérifier que votre gestionnaire refuse les livraisons hors de la fenêtre de 5 minutes, et modifiez le corps après la signature pour vérifier qu'il refuse une signature invalide.
Node
import { Webhook, WebhookVerificationError } from "@keenauth/node/webhooks";
import { sampleEvent, signWebhook, testWebhookSecret } from "@keenauth/node/testing";
const webhook = new Webhook(testWebhookSecret()); // en production : process.env.KEENAUTH_WEBHOOK_SECRET
const delivery = signWebhook(sampleEvent("user.created"));
const event = webhook.verify(delivery.body, delivery.headers); // l'événement analysé
// Une livraison datant d'une heure est refusée.
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) accepte une chaîne, un Uint8Array ou un objet (sérialisé avec JSON.stringify). Les options sont secret, id et timestamp (secondes Unix ou un Date). Pour tester un gestionnaire HTTP, envoyez delivery.body comme corps brut de la requête avec delivery.headers et Content-Type: application/json.
Python
import time
from keenauth import testing
from keenauth.webhooks import WebhookTimestampError, WebhookVerifier
verifier = WebhookVerifier(testing.webhook_secret()) # en production : os.environ["KEENAUTH_WEBHOOK_SECRET"]
delivery = testing.sign_webhook(testing.sample_event("user.created"))
verifier.verify(delivery.body, delivery.headers)
# Une livraison datant d'une heure est refusée.
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) accepte des octets, une chaîne ou un dictionnaire (sérialisé en JSON compact) et renvoie delivery.body (octets) et delivery.headers (un dictionnaire). Avec un client de test comme ceux de Flask ou FastAPI, envoyez delivery.body comme corps brut avec 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()}) // en production : 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) // votre gestionnaire, qui appelle v.Verify(r.Header, body)
if rec.Code != http.StatusOK {
t.Fatalf("status %d", rec.Code)
}
}
keenauthtest.SignWebhook(payload, ...) renvoie delivery.Body et delivery.Header ; delivery.Request(url) construit la requête POST avec Content-Type: application/json. Les options sont WithWebhookSecret, WithWebhookID et WithWebhookTimestamp.
Ce que le kit ne fait pas
- Il n'émule pas KeenAuth. Les flux de connexion, les règles d'émission des jetons, les limites de débit et tous les autres comportements du serveur ne sont réels que dans votre environnement hébergé
development; exécutez vos tests d'intégration dans cet environnement avant toute mise en production. - Il n'existe pas d'horloge de test. Pour contrôler le temps, passez votre propre horloge au vérificateur (
Now,now) et définissez la date d'émission du jeton. - Il ne crée pas de preuves DPoP. Vous pouvez ajouter une revendication
cnf.jkt, mais vous devez alors signer la preuve vous-même. - Il ne livre pas de webhooks. Les livraisons réelles, les nouvelles tentatives et la sélection des événements ne sont réelles que dans votre environnement hébergé
development.