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:
Bearerşemasıyla gönderilen DPoP'a bağlı bir token, geçerli bir kanıt eklenmiş olsa bileinvalid_tokenile reddedilir (RFC 9449 §7.2).DPoPşemasıyla gönderilen bir token, DPoP'a bağlı olmalı ve geçerli bir kanıtla gelmelidir (RFC 9449 §7.1).
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
- Issuer ve anahtarlar: token'ın
issdeğeri, yapılandırılmış issuer ile eşleşmelidir. Anahtarlar yalnızca o issuer'ın keşif belgesindekijwks_uriadresinden alınır vekidyalnızca bu anahtar kümesinde aranır. Başka bir ortamın anahtarıyla imzalanmış token reddedilir. - Algoritma: yalnızca RS256, PS256, ES256 ve EdDSA. Anahtarın
algdeğeri token'ınalgdeğeriyle eşleşmelidir;noneve HMAC algoritmaları her zaman reddedilir. - Erişim token'ları:
typdeğeriat+jwtolmalıdır (RFC 9068),audyapılandırdığınız hedef kitleyi içermelidir vesubileclient_idbulunmalıdır. - ID token'ları:
audistemci kimliğinizi içermelidir,azpvarsa ona eşit olmalıdır venonceilemax_agedeğerleri geçtiğinizde kontrol edilir. - Zaman:
expzorunludur;nbfveiatgelecekte olmamalıdır. En fazla 60 saniyelik saat sapmasına izin verilir. - DPoP: token bir anahtara bağlıysa (
cnf.jkt), geçerli bir DPoP kanıtı (RFC 9449) gerekir. Yeniden kullanılan kanıtlar reddedilir. Şema seçeneğiyle, bağlı bir token asla taşıyıcı token olarak kabul edilmez; "DPoP zorunlu" ayarıyla yalnızca bağlı token'lar kabul edilir. - Anahtar önbelleği: anahtarlar, sunucunun
max-agedeğeri kadar önbelleğe alınır (varsayılan 300 saniye, en fazla 600). Bilinmeyen birkiddeğeri, dakikada en fazla bir yeniden getirmeyi tetikler.
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.