Pruebas sin conexión con el kit de pruebas
Cada SDK de servidor (Go, TypeScript y Python) tiene un módulo testing para las pruebas unitarias y CI. Genera tokens de acceso y tokens de ID firmados con una clave de prueba fija, construye un verificador de tokens que acepta exclusivamente esos tokens sin ningún acceso a la red, le proporciona ejemplos de cargas útiles de eventos y firma las entregas de webhooks con un secreto de prueba público. Sus pruebas se ejecutan sin un entorno de KeenAuth, sin credenciales y sin acceso a Internet.
Cómo funciona
- Una clave de prueba fija y pública. Los tokens de prueba se firman con ES256 y con el ID de clave
keenauth-test-1. La clave es deliberadamente pública: su parte privada se deriva de una etiqueta publicada, y el conjunto de claves públicas está incluido en cada SDK. No confíe nunca en esta clave en ningún otro contexto. - Un emisor que no puede existir. Los tokens de prueba llevan el emisor
https://test.keenauth.invalid. El dominio de nivel superior.invalidestá reservado (RFC 2606) y nunca se resuelve, por lo que ningún entorno real puede tener este emisor. - Modo de prueba explícito. El verificador del SDK acepta este emisor solo cuando el modo de prueba se activa en el código. Un verificador configurado de la forma habitual rechaza cualquier emisor bajo
.invalidcomo error de configuración, de modo que un verificador de producción nunca puede aceptar un token de prueba, aunque su emisor se hubiera configurado por error como el emisor de prueba. Un token firmado con la clave de prueba, pero que lleve su emisor real, también se rechaza, porque su entorno publica claves distintas. - Sin red. En modo de prueba, el verificador toma la clave de prueba de la memoria y nunca descarga nada. Solo acepta el emisor de prueba.
El modo de prueba no debilita las demás comprobaciones: los tokens caducados, una audiencia incorrecta, una carga útil modificada, un jti ausente y un token vinculado a DPoP sin prueba se rechazan exactamente igual que en producción. Consulte Verificación de tokens en su API para ver la lista completa.
Mantenga el modo de prueba solo en el código de pruebas. Cualquiera puede firmar tokens de prueba, por lo que un servicio que lo activara aceptaría tokens falsificados.
Go
El paquete es keenauthtest, al estilo 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)
}
// Pass v to the handler under test and send "Authorization: Bearer " + token.
claims, err := v.VerifyAccessToken(context.Background(), token, nil)
if err != nil || claims.Scope != "orders:read" {
t.Fatal(err)
}
}
keenauthtest.IDToken(clientID, ...) genera un token de ID. WithLifetime cambia la duración (un valor negativo genera un token caducado) y WithIssuedAt establece la fecha de emisión. En WithClaims, un valor nil elimina una reclamación. Sin el paquete, puede establecer usted mismo verify.Config{TestingMode: true}.
TypeScript
El módulo es el subpaquete @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, ...) genera un token de ID. Las opciones son claims (un valor null elimina una reclamación), lifetimeSeconds e issuedAt. Sin el módulo, puede pasar testing: true a createVerifier.
Python
El módulo es keenauth.testing; necesita el extra verify, como el verificador.
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, ...) genera un token de ID. Los argumentos con nombre son claims (un valor None elimina una reclamación), lifetime_seconds e issued_at. Sin el módulo, puede pasar testing=True a VerifierConfig.
Reclamaciones predeterminadas
| Reclamación | Token de acceso | Token de ID |
|---|---|---|
iss |
https://test.keenauth.invalid |
https://test.keenauth.invalid |
sub |
usr_01m3vnbwg0e8mbe7h7xxp6rmkc |
usr_01m3vnbwg0e8mbe7h7xxp6rmkc |
aud |
la audiencia que pase | el ID de cliente que pase |
client_id |
app_01m3vnbwg0fqba5ntanfd63s8n |
— |
iat, exp |
ahora, ahora + 3600 segundos | ahora, ahora + 3600 segundos |
jti |
aleatorio | — |
auth_time |
— | igual que iat |
El encabezado del token de acceso tiene typ: at+jwt (RFC 9068). Añada las reclamaciones que lea su API, como scope, org_id, permissions, acr, amr o auth_time. Para los tokens de ID, añada nonce cuando su código lo compruebe.
Eventos de ejemplo
El kit incluye cargas útiles de ejemplo de eventos comunes: user.created, user.updated, user.deleted, session.created, session.revoked, organization.created y organization_membership.created. Usan el mismo sobre y los mismos esquemas que los eventos que envía KeenAuth, en el modo reducido predeterminado: identificadores, estado y nombres de los campos modificados, sin datos personales. Utilícelos para probar sus controladores de eventos.
- Go:
keenauthtest.Event("user.created")devuelve el JSON;keenauthtest.EventTypes()enumera los tipos. - TypeScript:
sampleEvent("user.created")ysampleEventTypes(). - Python:
testing.sample_event("user.created")ytesting.event_types().
Pruebas de webhooks
El kit firma una carga útil exactamente como una entrega de webhook de KeenAuth (Standard Webhooks v1): devuelve el cuerpo sin procesar y los encabezados webhook-id, webhook-timestamp y webhook-signature. Su controlador la verifica con el verificador de webhooks del SDK, el mismo código que se ejecuta en producción. Solo cambia el secreto.
- Un secreto de prueba público. De forma predeterminada, el kit firma con un secreto de prueba fijo
idp_whsec_cuya clave se deriva de la etiqueta publicadaKeenAuth offline test kit webhook secret (public, TEST ONLY). Cualquiera puede calcularlo, por lo que nunca debe configurarlo en un endpoint real. Configure su verificador con él en las pruebas o pase su propio secreto al asistente. - Igual que una entrega real.
webhook-ides eliddel evento (o un IDmsg_aleatorio cuando la carga útil no tiene uno) ywebhook-timestampes el momento actual, salvo que lo establezca. El cuerpo se envía exactamente como lo pase. - Repetición y manipulación. Firme con una marca de tiempo antigua para comprobar que su controlador rechaza las entregas fuera de la ventana de 5 minutos, y modifique el cuerpo después de firmarlo para comprobar que rechaza una firma incorrecta.
Node
import { Webhook, WebhookVerificationError } from "@keenauth/node/webhooks";
import { sampleEvent, signWebhook, testWebhookSecret } from "@keenauth/node/testing";
const webhook = new Webhook(testWebhookSecret()); // in production: process.env.KEENAUTH_WEBHOOK_SECRET
const delivery = signWebhook(sampleEvent("user.created"));
const event = webhook.verify(delivery.body, delivery.headers); // the parsed event
// A delivery from an hour ago is refused.
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) acepta una cadena, un Uint8Array o un objeto (serializado con JSON.stringify). Las opciones son secret, id y timestamp (segundos Unix o un Date). Para probar un controlador HTTP, envíe delivery.body como cuerpo sin procesar de la solicitud con delivery.headers y Content-Type: application/json.
Python
import time
from keenauth import testing
from keenauth.webhooks import WebhookTimestampError, WebhookVerifier
verifier = WebhookVerifier(testing.webhook_secret()) # in production: os.environ["KEENAUTH_WEBHOOK_SECRET"]
delivery = testing.sign_webhook(testing.sample_event("user.created"))
verifier.verify(delivery.body, delivery.headers)
# A delivery from an hour ago is refused.
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) acepta bytes, una cadena o un diccionario (serializado como JSON compacto) y devuelve delivery.body (bytes) y delivery.headers (un diccionario). Con un cliente de pruebas como el de Flask o el de FastAPI, envíe delivery.body como cuerpo sin procesar 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 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) // your handler, which calls v.Verify(r.Header, body)
if rec.Code != http.StatusOK {
t.Fatalf("status %d", rec.Code)
}
}
keenauthtest.SignWebhook(payload, ...) devuelve delivery.Body y delivery.Header; delivery.Request(url) construye la solicitud POST con Content-Type: application/json. Las opciones son WithWebhookSecret, WithWebhookID y WithWebhookTimestamp.
Lo que el kit no hace
- No emula KeenAuth. Los flujos de inicio de sesión, las reglas de emisión de tokens, los límites de frecuencia y cualquier otro comportamiento del servidor solo son reales en su entorno alojado
development; ejecute sus pruebas de integración allí antes de publicar. - No hay un reloj de pruebas. Para controlar el tiempo, pase su propio reloj al verificador (
Now,now) y establezca la fecha de emisión del token. - No genera pruebas DPoP. Puede añadir una reclamación
cnf.jkt, pero entonces debe firmar la prueba usted mismo. - No entrega webhooks. Las entregas reales, los reintentos y la selección de eventos solo son reales en su entorno alojado
development.