
# 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

```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.

```ts
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.

```python
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

```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

```ts
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

```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 bile `invalid_token` ile 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

```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

```ts
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

```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 `iss` değeri, yapılandırılmış issuer ile eşleşmelidir. Anahtarlar yalnızca o issuer'ın keşif belgesindeki `jwks_uri` adresinden alınır ve `kid` yalnı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 `alg` değeri token'ın `alg` değeriyle eşleşmelidir; `none` ve HMAC algoritmaları her zaman reddedilir.
- **Erişim token'ları:** `typ` değeri `at+jwt` olmalıdır (RFC 9068), `aud` yapılandırdığınız hedef kitleyi içermelidir ve `sub` ile `client_id` bulunmalıdır.
- **ID token'ları:** `aud` istemci kimliğinizi içermelidir, `azp` varsa ona eşit olmalıdır ve `nonce` ile `max_age` değerleri geçtiğinizde kontrol edilir.
- **Zaman:** `exp` zorunludur; `nbf` ve `iat` gelecekte 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-age` değeri kadar önbelleğe alınır (varsayılan 300 saniye, en fazla 600). Bilinmeyen bir `kid` değ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](/docs/sdk/testing) 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.
