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):

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

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.