
# Ara katman (middleware) ile API'nizi koruma

Her sunucu SDK'sı, KeenAuth erişim belirteçleriyle API'nizi koruyan hafif bir ara katman içerir: Go'da `net/http`, Node.js'te Express ve Fastify, Python'da ise Django, Flask ve FastAPI. Bu katman [belirteç doğrulayıcının](/docs/sdk/token-verification) üzerinde çalışır ve kendi belirteç mantığını içermez.

## Ara katman neler yapar

- **Belirteci okur.** `Authorization` başlığındaki `Bearer` veya `DPoP` şemasını kullanan belirteci okur. URL sorgusundaki belirteç (`?access_token=`) her zaman reddedilir, çünkü URL'ler günlüklere ve `Referer` başlıklarına düşebilir. Birden fazla `Authorization` başlığı veya birden fazla kimlik bilgisi içeren istekler de reddedilir.
- **DPoP'u denetler.** `DPoP` şemasında `DPoP` kanıt başlığı, istek yöntemi ve mutlak istek URL'si (yapılandırılmış genel temel URL ile istek yolunun birleşimi) doğrulayıcıya iletilir. DPoP'a bağlı bir belirteç `Bearer` şemasıyla gönderilirse reddedilir. DPoP'a bağlı olmayan bir belirteç `DPoP` şemasıyla gönderilirse de reddedilir.
- **Rotanın gereksinimlerini denetler:** gerekli kapsamların tamamı ve isteğe bağlı olarak adım yukarı doğrulama (step-up): kabul edilebilir `acr` değerleri ile oturum açmanın en fazla yaşı (RFC 9470).
- **Yapılandırdığınız iptal önbelleğine** belirtecin iptal edilip edilmediğini sorar.
- **Doğrulanmış talepleri isteğe ekler:** Go'da `protect.ClaimsFrom(ctx)`, Express ve Fastify'da `req.auth`, Django'da `request.auth`, Flask'ta `g.auth`, FastAPI'de ise bağımlılığın dönüş değeri.
- **Hataları standart `WWW-Authenticate` yanıtıyla ve küçük bir `application/problem+json` gövdesiyle yanıtlar.** Yanıt hiçbir zaman belirteci veya talep değerini içermez, ara katman da bunları asla günlüğe yazmaz.

## Go (net/http)

`protect` paketi yalnızca standart kütüphaneyi kullanır ve `http.Handler` kabul eden her yönlendiriciyle çalışır.

```go
import (
	"github.com/keenauth/keenauth-go/protect"
	"github.com/keenauth/keenauth-go/verify"
)

v, err := verify.New(verify.Config{
	Issuer:   "https://acme.eu.keenauth.app",
	Audience: "https://api.acme.example",
})
if err != nil {
	return err
}
m, err := protect.New(v, protect.Options{
	PublicBaseURL:       "https://api.acme.example",
	ResourceMetadataURL: "https://api.acme.example/.well-known/oauth-protected-resource",
})
if err != nil {
	return err
}

mux := http.NewServeMux()
mux.Handle("GET /orders", m.Require(protect.Requirement{Scopes: []string{"orders:read"}})(http.HandlerFunc(
	func(w http.ResponseWriter, r *http.Request) {
		claims, _ := protect.ClaimsFrom(r.Context())
		fmt.Fprintln(w, claims.Subject)
	})))

age := 10 * time.Minute
mux.Handle("POST /payouts", m.Require(protect.Requirement{
	Scopes:        []string{"payouts:write"},
	AcceptableACR: []string{"urn:idp:acr:mfa"},
	MaxAuthAge:    &age,
})(payoutsHandler))
```

`m.Handler(next)`, geçerli bir belirteç dışında ek gereksinim olmadan bir işleyiciyi korur. `Require`, bir kapsam geçerli bir kapsam belirteci değilse başlangıçta panic oluşturur; tıpkı `http.ServeMux`'un geçersiz bir desen için yaptığı gibi.

## Node.js (Express ve Fastify)

Ara katman `@keenauth/node/middleware` alt yolundadır. Express ve Fastify, SDK'nın bağımlılıkları değildir.

```ts
import { createVerifier } from "@keenauth/node";
import { createProtector, expressAuth, fastifyAuth } from "@keenauth/node/middleware";

const verifier = createVerifier({ issuer: "https://acme.eu.keenauth.app", audience: "https://api.acme.example" });
const protector = createProtector(verifier, { publicBaseUrl: "https://api.acme.example" });

// Express
app.get("/orders", expressAuth(protector, { scopes: ["orders:read"] }), (req, res) => {
  res.json({ sub: req.auth.subject });
});

// Fastify
fastify.decorateRequest("auth", null);
fastify.get("/orders", { preHandler: fastifyAuth(protector, { scopes: ["orders:read"] }) }, async (request) => {
  return { sub: request.auth.subject };
});
```

Adım yukarı doğrulama için `{ acceptableAcr: ["urn:idp:acr:mfa"], maxAuthAgeSeconds: 600 }` kullanın. TypeScript'te `req.auth` değerini okumak için isteği alt yoldan dışa aktarılan `AuthenticatedRequest` türüyle tanımlayın.

## Python (Django, Flask, FastAPI)

Kullandığınız çatı için ek paketi kurun: `pip install "keenauth-python[django]"`, `"keenauth-python[flask]"` veya `"keenauth-python[fastapi]"`. Çatı yalnızca kendi uyarlayıcı modülü tarafından içe aktarılır.

```python
from keenauth.middleware import Protector
from keenauth.verify import Verifier, VerifierConfig

verifier = Verifier(VerifierConfig(issuer="https://acme.eu.keenauth.app", audience="https://api.acme.example"))
protector = Protector(verifier, public_base_url="https://api.acme.example")
```

Django:

```python
from django.http import JsonResponse
from keenauth.middleware.django import protect

@protect(protector, scopes=["orders:read"])
def orders(request):
    return JsonResponse({"sub": request.auth.subject})
```

Flask:

```python
from flask import g
from keenauth.middleware.flask import protect

@app.get("/orders")
@protect(protector, scopes=["orders:read"])
def orders():
    return {"sub": g.auth.subject}
```

FastAPI:

```python
from fastapi import Depends, FastAPI
from keenauth.middleware.fastapi import RequireAuth, install
from keenauth.verify import AccessClaims

app = FastAPI()
install(app)

@app.get("/orders")
def orders(claims: AccessClaims = Depends(RequireAuth(protector, scopes=["orders:read"]))):
    return {"sub": claims.subject}
```

`install(app)`, hata gövdesini yazan işleyiciyi ve her bir sınama için bir `WWW-Authenticate` başlığını kaydeder. Adım yukarı doğrulama için `acceptable_acr=["urn:idp:acr:mfa"], max_auth_age=timedelta(minutes=10)` kullanın.

## Genel temel URL (zorunlu)

Bir DPoP kanıtı, istemcinin çağırdığı URL'yi (`htu`) belirtir. Ara katman bu URL'yi, yapılandırdığınız genel temel URL'nin (`PublicBaseURL`, `publicBaseUrl`, `public_base_url`) ve istek yolunun birleşimiyle karşılaştırır. Bunu istemcilerin kullandığı adrese ayarlayın; örneğin `https://api.acme.example` veya `https://acme.example/api`. Bu alan zorunludur: istemcileriniz yalnızca Bearer belirteçleri gönderse bile, bu olmadan ara katmanı oluşturmak başlangıçta bir yapılandırma hatasıyla başarısız olur.

Ara katman bu URL'yi asla isteğin `Host` başlığından veya `X-Forwarded-*` başlıklarından oluşturmaz. Python'da ise çatının kendi istek URL'sini kullanmaz; çatı veya sunucu iletilen başlıklara güvenecek şekilde yapılandırılmış olsa bile. İstemci bu değerleri seçer: API'niz iki ana makine adı altında sunuluyorsa, bunlardan biri için yapılmış bir kanıt diğerine tekrar oynatılabilir. Bir yük dengeleyicinin veya ters vekilin arkasında, genel temel URL aynı zamanda ara katmana istemcilerin gördüğü şemayı, ana makineyi ve yol önekini de bildirir. Genel temel URL `https` kullanmalıdır; `localhost`, `127.0.0.1` ve `[::1]` istisnadır (RFC 6750 §5.3). Şema ve ana makine büyük/küçük harf duyarsız karşılaştırılır ve varsayılan bağlantı noktası (`:443`, `:80`) yok sayılır; bu hem temel URL'de hem de kanıtın `htu` değerinde geçerlidir.

## Yanıtlar

| Durum | Durum kodu | `WWW-Authenticate` | `code` |
|---|---|---|---|
| Belirteç yok veya desteklenmeyen şema | `401` | `Bearer` ve `DPoP algs="…"`, hata olmadan | `missing_token` |
| Sorgudaki belirteç, tekrarlanan veya hatalı `Authorization` | `400` | `Bearer error="invalid_request"` | `invalid_request` |
| Geçersiz, süresi dolmuş veya iptal edilmiş belirteç | `401` | `error="invalid_token"` | `invalid_token` |
| Eksik, tekrarlanan veya geçersiz DPoP kanıtı | `401` | `DPoP error="invalid_dpop_proof"` | `invalid_dpop_proof` |
| Gerekli bir kapsam eksik | `403` | `error="insufficient_scope", scope="…"` | `insufficient_scope` |
| Adım yukarı doğrulama gerekli | `401` | `error="insufficient_user_authentication", acr_values="…", max_age="…"` | `insufficient_user_authentication` |
| İmza anahtarları veya iptal durumu kullanılamıyor | `503` | yok | `jwks_unavailable`, `revocation_unavailable` |

Sınama, istemcinin kullandığı şemayı kullanır. Kaynak meta veri URL'sini ayarladığınızda, her `401` sınaması `resource_metadata` (RFC 9728) içerir. Gövde `type`, `title`, `status`, `code` ve sabit bir `detail` alanı içerir; bir belirtecin neden geçersiz olduğunu hiçbir zaman belirtmez. Kendi günlükleriniz için bir hata geri çağırma işlevi (`OnError`, `onError`, `on_error`) iletin: bu işlev doğrulayıcının hatasını alır ve bu hata belirteç veya talep değeri içermez. `Authorization` ya da `DPoP` başlıklarını kendiniz günlüğe yazmayın.

## İptal önbelleği (`token.revoked`)

JWT erişim belirteci, süresi dolana kadar geçerli kalır. İptal edilmiş belirteçleri daha erken reddetmek için `token.revoked` webhook'unu işleyin ve seçicisini, ara katmanın sorguladığı bir iptal önbelleğine kaydedin. SDK'lar bellek içi bir önbellek içerir: Go için `protect.NewMemoryRevocationCache`, Node.js için `createMemoryRevocationCache` ve Python için `MemoryRevocationCache`.

Bir seçici bir belirteç kimliğini (`jti`), bir oturumu (`sid`), bir kullanıcıyı (`sub`), bir kuruluşu (`org_id`), bir uygulamayı (`client_id`) veya kuruluş + kullanıcı ve uygulama + kullanıcı çiftlerini belirtir. `jti` seçicisi o belirteci iptal eder. Diğer seçiciler, olayın `not_before` zamanından önce verilmiş tüm belirteçleri iptal eder; sonradan verilen belirteçler kabul edilir. İşaretçiler varsayılan olarak 24 saat ve 1 dakika tutulur; bu, mümkün olan en uzun erişim belirteci ömrü ile saat kayması toplamıdır.

```go
cache := protect.NewMemoryRevocationCache(0, nil)
m, _ := protect.New(v, protect.Options{PublicBaseURL: "https://api.acme.example", Revocation: cache})

// In your verified token.revoked webhook handler:
err := cache.Revoke(protect.Selector{SessionID: "ses_…"}, notBefore)
```

Bellek içi önbellek tek bir sürece aittir. API'niz birden fazla örnekte çalışıyorsa, iptal denetleyici arayüzünü paylaşılan bir depo üzerinde uygulayın. Önbellek yanıt veremezse, ara katman isteği `503` ile reddeder.

## Test etme

Ara katman testleriniz için belirteç üretmek üzere [çevrimdışı test kitini](/docs/sdk/testing) kullanın. Kitte DPoP kanıtı yardımcısı yoktur; testinizde kanıtı dilinizin kriptografi kütüphanesiyle imzalayın.
