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 üzerinde çalışır ve kendi belirteç mantığını içermez.
Ara katman neler yapar
- Belirteci okur.
AuthorizationbaşlığındakiBearerveyaDPoPşemasını kullanan belirteci okur. URL sorgusundaki belirteç (?access_token=) her zaman reddedilir, çünkü URL'ler günlüklere veRefererbaşlıklarına düşebilir. Birden fazlaAuthorizationbaşlığı veya birden fazla kimlik bilgisi içeren istekler de reddedilir. - DPoP'u denetler.
DPoPşemasındaDPoPkanı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
acrdeğ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'dareq.auth, Django'darequest.auth, Flask'tag.auth, FastAPI'de ise bağımlılığın dönüş değeri. - Hataları standart
WWW-Authenticateyanıtıyla ve küçük birapplication/problem+jsongö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.
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.
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.
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:
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:
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:
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.
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 kullanın. Kitte DPoP kanıtı yardımcısı yoktur; testinizde kanıtı dilinizin kriptografi kütüphanesiyle imzalayın.