API'nizde token'ları doğrulama

SDK token doğrulayıcısı, KeenAuth ortamınız tarafından verilen erişim token'larını ve ID token'larını denetler. Koruduğunuz API'de kullanın. Üç SDK'nın (Go, TypeScript ve Python) tamamında bu bulunur ve hepsi aynı kurallara ve aynı test vektörlerine uyar.

Go

import "github.com/keenauth/keenauth-go/verify"

v, err := verify.New(verify.Config{
	Issuer:   "https://acme.eu.keenauth.app", // ortamınızın issuer değeri, birebir
	Audience: "https://api.acme.example",     // API'nizin tanımlayıcısı
})
if err != nil {
	return err
}

// HTTP handler'ınızda:
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

ID token'ları için yapılandırmada ClientID değerini ayarlayın ve v.VerifyIDToken(ctx, idToken, verify.IDTokenOptions{Nonce: nonce}) çağrısını yapın.

TypeScript

TypeScript doğrulayıcısı yalnızca platformun WebCrypto ve fetch özelliklerini kullanır (Node.js 22 veya üzeri) ve herhangi bir bağımlılığı yoktur.

import { createVerifier, VerifyError } from "@keenauth/node";

const verifier = createVerifier({
  issuer: "https://acme.eu.keenauth.app", // ortamınızın issuer değeri, birebir
  audience: "https://api.acme.example",   // API'nizin tanımlayıcısı
});

// HTTP handler'ınızda:
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");
}

ID token'ları için yapılandırmada clientId değerini ayarlayın ve verifier.verifyIdToken(idToken, { nonce }) çağrısını yapın.

Python

Python doğrulayıcısı cryptography paketine ihtiyaç duyar; bu paket verify ek bileşeniyle gelir: pip install "keenauth-python[verify]". Çağrılar eşzamanlıdır ve iş parçacığı açısından güvenlidir.

from keenauth.verify import DPoPInput, Verifier, VerifierConfig, VerifyError

verifier = Verifier(VerifierConfig(
    issuer="https://acme.eu.keenauth.app",  # ortamınızın issuer değeri, birebir
    audience="https://api.acme.example",    # API'nizin tanımlayıcısı
))

# HTTP handler'ınızda:
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"'})

ID token'ları için yapılandırmada client_id değerini ayarlayın ve verifier.verify_id_token(id_token, nonce=nonce) çağrısını yapın.

Adım yükseltme (RFC 9470)

Hassas bir işlemi korumak için her çağrıda daha güçlü veya daha yeni bir giriş isteyin. Kabul edilebilir kimlik doğrulama bağlam sınıfı referanslarını (acr) ve girişin en fazla yaşını (auth_time) geçin. Aşağıdaki dpop, yukarıdaki örneklerdeki DPoP girdisidir (DPoP kullanmıyorsanız nil/undefined/None).

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

Denetimler diğer tüm kontrollerden sonra çalışır; bu nedenle geçersiz bir token her zaman invalid_token sonucunu verir. Geçerli ama gereksinimi karşılamayan bir token insufficient_user_authentication kodunu alır ve hata, gerekli acr_values ve max_age değerlerini taşır (yalnızca sizin ayarladıklarınız). StepUpChallenge (Go), stepUpChallenge (TypeScript) ve step_up_challenge (Python), bunu RFC 9470 §3 kapsamında WWW-Authenticate başlık değerine dönüştürür; örneğin Bearer error="insufficient_user_authentication", error_description="authentication too old", max_age="900". Bu challenge'ı anlayan bir istemci, bu acr_values ve max_age değerleriyle yeni bir yetkilendirme isteği başlatır. acr, değerlerden biriyle birebir (büyük/küçük harfe duyarlı) eşleşmelidir; en fazla yaş ayarladığınızda auth_time zorunludur ve saat sapması toleransı ID token'larındaki gibi uygulanır. urn:idp:acr:1fa, urn:idp:acr:mfa ve urn:idp:acr:phr değerleri, ortamınızın acr_values_supported alanında yayımladığı değerlerdir.

DPoP ve Authorization şeması (RFC 9449)

DPoP'a bağlı bir token (cnf.jkt), istemcisinin özel anahtarı olmadan işe yaramaz; bu nedenle çalınan bir kopya tekrar oynatılamaz. Bunu iki kural güvence altına alır. Doğrulayıcı, isteğin Authorization başlığındaki şemayı (Bearer veya DPoP) geçtiğinizde bu kuralları uygular:

Bir API'de yalnızca gönderici kısıtlı token'ları kabul etmek için (hassas API'ler için önerilir) "DPoP zorunlu" ayarını da etkinleştirin: DPoP'a bağlı olmayan bir token dpop_required kodunu alır. DPoP challenge yardımcısı, her 401 hatasını RFC 9449 §7.1 kapsamında WWW-Authenticate değerine dönüştürür; örneğin 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" veya "DPoP", Authorization başlığından
	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})

Kaynak sunucu ara katmanı (Go protect, TypeScript @keenauth/node/middleware, Python keenauth.middleware) şemayı sizin yerinize okur. "DPoP zorunlu" ayarı (API bazında veya rota bazında), bir Bearer isteğine yalnızca 401 ve DPoP challenge'ı ile yanıt verir.

Doğrulayıcının kontrol ettikleri

Hatalar

Her hatanın bir Code değeri vardır. Hata mesajı hiçbir zaman token'ı veya taşıdığı talepleri içermez.

Kod Anlamı Yanıt
invalid_token Token geçersiz, süresi dolmuş veya bu API için değil. 401
jwks_unavailable Issuer'ın anahtarları alınamadı ve önbellekte hiçbiri yok. 503
dpop_invalid DPoP kanıtı eksik veya geçersiz. DPoP challenge yardımcısından gelen WWW-Authenticate ile 401
dpop_required DPoP zorunlu ve token DPoP'a bağlı değil. DPoP challenge yardımcısından gelen WWW-Authenticate ile 401
insufficient_user_authentication Token geçerli ancak acr veya auth_time değeri gereksiniminizi karşılamıyor (RFC 9470). Adım yükseltme yardımcısından gelen WWW-Authenticate ile 401
config_invalid Doğrulayıcı yapılandırması hatalı. Yapılandırmayı düzeltin.

Başka bir JWT kütüphanesi kullanıyorsanız

Aynı kontrol listesini uygulayın: issuer'ı keşif belgesine bağlayın, anahtarları yalnızca o jwks_uri adresinden alın, anahtarın alg değerinin token'ın alg değerine eşit olmasını zorunlu kılın ve none ile HMAC algoritmalarını reddedin.

Çevrimdışı test

Birim testleri ve CI için çevrimdışı test kitini kullanın. Bu kit, yalnızca açık test modundaki bir doğrulayıcının kabul ettiği test token'ları üretir; üretim ortamındaki bir doğrulayıcı bunları her zaman reddeder.