Token in Ihrer API verifizieren
Der Token-Verifier des SDK prüft Access-Tokens und ID-Tokens, die von Ihrer KeenAuth-Umgebung ausgestellt wurden. Verwenden Sie ihn in der API, die Sie schützen. Alle drei SDKs (Go, TypeScript und Python) enthalten ihn, und sie folgen denselben Regeln und denselben Testvektoren.
Go
import "github.com/keenauth/keenauth-go/verify"
v, err := verify.New(verify.Config{
Issuer: "https://acme.eu.keenauth.app", // your environment's issuer, exactly
Audience: "https://api.acme.example", // the identifier of your API
})
if err != nil {
return err
}
// In your HTTP handler:
claims, err := v.VerifyAccessToken(r.Context(), token, &verify.DPoPInput{
Proof: r.Header.Get("DPoP"),
Method: r.Method,
URL: "https://api.acme.example" + r.URL.Path,
})
if err != nil {
w.Header().Set("WWW-Authenticate", `Bearer error="invalid_token"`)
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
// claims.Subject, claims.ClientID, claims.Scope
Für ID-Tokens setzen Sie ClientID in der Konfiguration und rufen v.VerifyIDToken(ctx, idToken, verify.IDTokenOptions{Nonce: nonce}) auf.
TypeScript
Der TypeScript-Verifier verwendet nur das WebCrypto und fetch der Plattform (Node.js 22 oder neuer) und hat keine Abhängigkeiten.
import { createVerifier, VerifyError } from "@keenauth/node";
const verifier = createVerifier({
issuer: "https://acme.eu.keenauth.app", // your environment's issuer, exactly
audience: "https://api.acme.example", // the identifier of your API
});
// In your HTTP handler:
try {
const claims = await verifier.verifyAccessToken(token, {
proof: req.get("DPoP") ?? "",
method: req.method,
url: "https://api.acme.example" + req.path,
});
// claims.subject, claims.clientId, claims.scope
} catch (e) {
if (!(e instanceof VerifyError)) throw e;
res.set("WWW-Authenticate", 'Bearer error="invalid_token"').status(401).send("unauthorized");
}
Für ID-Tokens setzen Sie clientId in der Konfiguration und rufen verifier.verifyIdToken(idToken, { nonce }) auf.
Python
Der Python-Verifier benötigt das Paket cryptography, das mit dem Extra verify installiert wird: pip install "keenauth-python[verify]". Die Aufrufe sind synchron und threadsicher.
from keenauth.verify import DPoPInput, Verifier, VerifierConfig, VerifyError
verifier = Verifier(VerifierConfig(
issuer="https://acme.eu.keenauth.app", # your environment's issuer, exactly
audience="https://api.acme.example", # the identifier of your API
))
# In your HTTP handler:
try:
claims = verifier.verify_access_token(
token,
DPoPInput(
proof=request.headers.get("DPoP", ""),
method=request.method,
url="https://api.acme.example" + request.path,
),
)
# claims.subject, claims.client_id, claims.scope
except VerifyError:
return Response("unauthorized", status=401, headers={"WWW-Authenticate": 'Bearer error="invalid_token"'})
Für ID-Tokens setzen Sie client_id in der Konfiguration und rufen verifier.verify_id_token(id_token, nonce=nonce) auf.
Step-up (RFC 9470)
Um eine sensible Operation zu schützen, verlangen Sie pro Aufruf eine stärkere oder aktuellere Anmeldung. Übergeben Sie die akzeptablen Authentication-Context-Class-References (acr) und das maximale Alter der Anmeldung (auth_time). dpop unten ist die DPoP-Eingabe aus den obigen Beispielen (nil/undefined/None, wenn Sie DPoP nicht verwenden).
Go
age := 15 * time.Minute
claims, err := v.VerifyAccessToken(r.Context(), token, dpop, verify.AccessTokenOptions{
AcceptableACR: []string{"urn:idp:acr:mfa", "urn:idp:acr:phr"},
MaxAuthAge: &age,
})
if challenge, ok := verify.StepUpChallenge(err); ok {
w.Header().Set("WWW-Authenticate", challenge)
http.Error(w, "step-up required", http.StatusUnauthorized)
return
}
TypeScript
import { createVerifier, stepUpChallenge } from "@keenauth/node";
try {
const claims = await verifier.verifyAccessToken(token, dpop, {
acceptableAcr: ["urn:idp:acr:mfa", "urn:idp:acr:phr"],
maxAuthAgeSeconds: 900,
});
} catch (e) {
const challenge = stepUpChallenge(e);
if (challenge === undefined) throw e;
res.set("WWW-Authenticate", challenge).status(401).send("step-up required");
}
Python
from datetime import timedelta
from keenauth.verify import VerifyError, step_up_challenge
try:
claims = verifier.verify_access_token(
token,
dpop,
acceptable_acr=["urn:idp:acr:mfa", "urn:idp:acr:phr"],
max_auth_age=timedelta(minutes=15),
)
except VerifyError as err:
challenge = step_up_challenge(err)
if challenge is None:
raise
return Response("step-up required", status=401, headers={"WWW-Authenticate": challenge})
Die Prüfungen laufen nach allen anderen Prüfungen, daher ist ein ungültiges Token immer invalid_token. Ein gültiges Token, das die Anforderung nicht erfüllt, erhält den Code insufficient_user_authentication, und der Fehler enthält die geforderten acr_values und max_age (nur die, die Sie gesetzt haben). StepUpChallenge (Go), stepUpChallenge (TypeScript) und step_up_challenge (Python) wandeln ihn in den Wert des Headers WWW-Authenticate gemäß RFC 9470 §3 um, zum Beispiel Bearer error="insufficient_user_authentication", error_description="authentication too old", max_age="900". Ein Client, der die Challenge versteht, startet eine neue Autorisierungsanfrage mit diesen acr_values und max_age. acr muss exakt einem der Werte entsprechen (Groß-/Kleinschreibung wird beachtet); auth_time ist erforderlich, wenn Sie ein maximales Alter setzen, und die Toleranz für Uhrenabweichung gilt wie bei ID-Tokens. Die Werte urn:idp:acr:1fa, urn:idp:acr:mfa und urn:idp:acr:phr sind diejenigen, die Ihre Umgebung in acr_values_supported veröffentlicht.
DPoP und das Authorization-Schema (RFC 9449)
Ein DPoP-gebundenes Token (cnf.jkt) ist ohne den privaten Schlüssel seines Clients wertlos, sodass eine gestohlene Kopie nicht wiederverwendet werden kann. Zwei Regeln sichern dies ab, und der Verifier erzwingt beide, wenn Sie das Schema des Authorization-Headers der Anfrage übergeben (Bearer oder DPoP):
- Ein DPoP-gebundenes Token, das mit dem Schema
Bearergesendet wird, wird mitinvalid_tokenabgelehnt, selbst wenn ein gültiger Proof beiliegt (RFC 9449 §7.2). - Ein Token, das mit dem Schema
DPoPgesendet wird, muss DPoP-gebunden sein und einen gültigen Proof mitbringen (RFC 9449 §7.1).
Um auf einer API nur sender-constrained Tokens zu akzeptieren (empfohlen für sensible APIs), setzen Sie zusätzlich "require DPoP": Ein Token, das nicht DPoP-gebunden ist, erhält den Code dpop_required. Der DPoP-Challenge-Helper wandelt jeden 401-Fehler in den Wert WWW-Authenticate gemäß RFC 9449 §7.1 um, zum Beispiel DPoP error="invalid_token", error_description="a DPoP-bound access token is required", algs="ES256 EdDSA PS256 RS256".
Go
claims, err := v.VerifyAccessToken(r.Context(), token, dpop, verify.AccessTokenOptions{
Scheme: scheme, // "Bearer" or "DPoP", from the Authorization header
RequireDPoP: true,
})
if challenge, ok := verify.DPoPChallenge(err); ok {
w.Header().Set("WWW-Authenticate", challenge)
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
TypeScript
import { dpopChallenge } from "@keenauth/node";
try {
const claims = await verifier.verifyAccessToken(token, dpop, { scheme, requireDPoP: true });
} catch (e) {
const challenge = dpopChallenge(e);
if (challenge === undefined) throw e;
res.set("WWW-Authenticate", challenge).status(401).send("unauthorized");
}
Python
from keenauth.verify import VerifyError, dpop_challenge
try:
claims = verifier.verify_access_token(token, dpop, scheme=scheme, require_dpop=True)
except VerifyError as err:
challenge = dpop_challenge(err)
if challenge is None:
raise
return Response("unauthorized", status=401, headers={"WWW-Authenticate": challenge})
Die Resource-Server-Middleware (Go protect, TypeScript @keenauth/node/middleware, Python keenauth.middleware) liest das Schema für Sie aus. Ihre Einstellung "require DPoP" (pro API oder pro Route) beantwortet eine Bearer-Anfrage ausschließlich mit 401 und der DPoP-Challenge.
Was der Verifier prüft
- Issuer und Schlüssel: Das
issdes Tokens muss dem konfigurierten Issuer entsprechen. Schlüssel werden nur vomjwks_uriim Discovery-Dokument dieses Issuers abgerufen, und diekidwird nur in diesem Schlüsselsatz gesucht. Ein Token, das mit dem Schlüssel einer anderen Umgebung signiert ist, wird abgelehnt. - Algorithmus: Nur RS256, PS256, ES256 und EdDSA. Das
algdes Schlüssels muss demalgdes Tokens entsprechen;noneund HMAC-Algorithmen werden immer abgelehnt. - Access-Tokens:
typmussat+jwtsein (RFC 9068),audmuss Ihre konfigurierte Audience enthalten, undsubundclient_idmüssen vorhanden sein. - ID-Tokens:
audmuss Ihre Client-ID enthalten,azpmuss ihr entsprechen, sofern vorhanden, undnoncesowiemax_agewerden geprüft, wenn Sie sie übergeben. - Zeit:
expist erforderlich;nbfundiatdürfen nicht in der Zukunft liegen. Eine Uhrenabweichung von höchstens 60 Sekunden ist zulässig. - DPoP: Ist das Token an einen Schlüssel gebunden (
cnf.jkt), ist ein gültiger DPoP-Proof (RFC 9449) erforderlich. Wiederverwendete Proofs werden abgelehnt. Mit der Schema-Option wird ein gebundenes Token nie als Bearer-Token akzeptiert; mit "require DPoP" werden nur gebundene Tokens akzeptiert. - Schlüssel-Cache: Schlüssel werden für das
max-agedes Servers zwischengespeichert (standardmäßig 300 Sekunden, höchstens 600). Eine unbekanntekidlöst höchstens einen erneuten Abruf pro Minute aus.
Fehler
Jeder Fehler hat einen Code. Die Meldung enthält niemals das Token oder seine Claims.
| Code | Bedeutung | Antworten mit |
|---|---|---|
invalid_token |
Das Token ist ungültig, abgelaufen oder nicht für diese API bestimmt. | 401 |
jwks_unavailable |
Die Schlüssel des Issuers konnten nicht abgerufen werden, und es sind keine zwischengespeichert. | 503 |
dpop_invalid |
Der DPoP-Proof fehlt oder ist ungültig. | 401 mit WWW-Authenticate aus dem DPoP-Challenge-Helper |
dpop_required |
Sie verlangen DPoP, und das Token ist nicht DPoP-gebunden. | 401 mit WWW-Authenticate aus dem DPoP-Challenge-Helper |
insufficient_user_authentication |
Das Token ist gültig, aber sein acr oder auth_time erfüllt Ihre Anforderung nicht (RFC 9470). |
401 mit WWW-Authenticate aus dem Step-up-Helper |
config_invalid |
Die Konfiguration des Verifiers ist fehlerhaft. | Korrigieren Sie die Konfiguration. |
Wenn Sie eine andere JWT-Bibliothek verwenden
Wenden Sie dieselbe Checkliste an: Binden Sie den Issuer an sein Discovery-Dokument, beziehen Sie Schlüssel nur von diesem jwks_uri, verlangen Sie, dass das alg des Schlüssels dem alg des Tokens entspricht, und lehnen Sie none sowie HMAC-Algorithmen ab.
Offline testen
Für Unit-Tests und CI verwenden Sie das Offline-Testkit. Es erstellt Test-Tokens, die nur ein Verifier im expliziten Testmodus akzeptiert; ein Produktions-Verifier lehnt sie immer ab.