
# Django API'sini koruma

Bu hızlı başlangıç, Django API'nizi KeenAuth erişim belirteçleriyle korur. Python SDK'nın `@protect` görünüm süsleyicisini ekler, bir görünümde bir kapsam zorunlu kılar ve API'yi SDK test kiti ile çevrimdışı test edersiniz. Örnek Django 5.2 kullanır ve veritabanı gerektirmez.

API'nin üç uç noktası vardır:

| Uç nokta | Gereksinim |
|---|---|
| `GET /api/public` | yok |
| `GET /api/private` | API'niz için geçerli bir erişim belirteci |
| `GET /api/messages` | `messages:read` kapsamına sahip geçerli bir erişim belirteci |

## Başlamadan önce

- Bir KeenAuth ortamı. Bu ortamın yayıncısı `https://your-environment.eu.keenauth.app` biçimindedir.
- Ortamda kayıtlı bir API ([API oluşturun](/docs/api/management/apis/create)). Bu API'nin `identifier` değeri, örneğin `https://api.example.com`, API'nizin kabul ettiği belirteçlerin hedef kitlesidir.
- Bu API üzerinde, onu çağıran uygulamaya verilmiş `messages:read` kapsamı.
- Python 3.10 veya üzeri.

## 1. SDK'yı kurun

```sh
pip install "keenauth-python[django]"
```

SDK henüz yayımlanmadı. Yayımlanana kadar örnek, SDK'yı kaynak kodundan kurar. Örnek, her paketi `requirements.txt` içinde tam bir sürüme sabitler.

## 2. Yapılandırın

`project/settings.py` her değeri ortam değişkenlerinden okur:

| Değişken | Zorunlu | Anlamı |
|---|---|---|
| `DJANGO_SECRET_KEY` | evet | Django'nun gizli anahtarı; bir tane üretin ve asla commit etmeyin |
| `DJANGO_ALLOWED_HOSTS` | hayır | Virgülle ayrılmış sunucu adları, varsayılan `localhost,127.0.0.1` |
| `KEENAUTH_ISSUER` | evet | Ortamınızın yayıncısı |
| `KEENAUTH_AUDIENCE` | evet | API'nizin tanımlayıcısı |
| `PUBLIC_BASE_URL` | evet | İstemcilerin çağırdığı URL (bkz. [Proxy arkasında](#proxy-arkasında)) |
| `KEENAUTH_WEBHOOK_SECRET` | önerilir | `token.revoked` teslim eden web kancası uç noktasının `idp_whsec_` imza sırrı (döndürme sırasında virgülle ayrılmış iki değer). Bu olmadan API iptal edilmiş belirteçlerden haberdar olmaz |

## 3. Görünümlerinizi koruyun

`@protect(protector)` geçerli bir belirteç ister ve doğrulanmış talepleri `request.auth` içine koyar; `scopes=[...]` ayrıca kapsamları da ister. Başarısız bir denetim, görünüm çalışmadan önce bir `application/problem+json` yanıtı döndürür.

```python
@require_GET
def public(request: HttpRequest) -> JsonResponse:
    # No token needed.
    return JsonResponse({"message": "Hello from a public endpoint."})


@require_GET
@protect(protector)
def private(request: HttpRequest) -> JsonResponse:
    # Any valid access token for this API; the verified claims are in request.auth.
    return JsonResponse({"message": "Hello from a private endpoint.", "sub": request.auth.subject})


@require_GET
@protect(protector, scopes=["messages:read"])
def messages(request: HttpRequest) -> JsonResponse:
    # A valid access token that also carries the messages:read scope.
    return JsonResponse({"messages": ["Hello from a scoped endpoint."]})
```

`api/auth.py` süsleyiciyi başlangıçta ayarlardan (`api/apps.py` içindeki `ApiConfig.ready`) oluşturur; böylece eksik bir ayar, sunucu ilk isteği almadan önce durur. Doğrulayıcı, bir belirteci ilk gördüğünde imza anahtarlarını ortamınızdan alır.

```python
@cache
def get_protector() -> Protector:
    if not settings.KEENAUTH_ISSUER or not settings.KEENAUTH_AUDIENCE or not settings.KEENAUTH_PUBLIC_BASE_URL:
        raise ImproperlyConfigured("KEENAUTH_ISSUER, KEENAUTH_AUDIENCE and PUBLIC_BASE_URL are required")
    # The verifier refuses an issuer under the reserved .invalid domain, so a test token can never pass here.
    verifier = Verifier(VerifierConfig(issuer=settings.KEENAUTH_ISSUER, audience=settings.KEENAUTH_AUDIENCE))
    return Protector(
        verifier,
        public_base_url=settings.KEENAUTH_PUBLIC_BASE_URL,
        revocation=revocation,
        # The error never contains the token or claim values; never log the Authorization or DPoP header.
        on_error=lambda err: log.warning("rejected: %s", err),
    )


@cache
def get_webhook() -> Optional[RevocationWebhook]:
    """The token.revoked webhook, or None when KEENAUTH_WEBHOOK_SECRET is not set. A malformed secret raises
    WebhookSecretError (without echoing it), so ApiConfig.ready stops the server at startup."""
    if not settings.KEENAUTH_WEBHOOK_SECRETS:
        return None
    return RevocationWebhook(settings.KEENAUTH_WEBHOOK_SECRETS, revocation)


class ProtectorProxy:
    """Hands each request to `get_protector()`, so the `@protect` decorators can be applied at import time."""

    def authenticate(self, req: RequestInfo, requirement: Optional[Requirement] = None) -> AccessClaims:
        return get_protector().authenticate(req, requirement)


protector = ProtectorProxy()
```

## 4. Çalıştırın ve API'yi çağırın

```sh
export DJANGO_SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(50))')"
export KEENAUTH_ISSUER=https://your-environment.eu.keenauth.app
export KEENAUTH_AUDIENCE=https://api.example.com
export PUBLIC_BASE_URL=https://api.example.com
python manage.py runserver 8080
```

`runserver` yalnızca geliştirme içindir; üretimde `project.wsgi` komutunu bir WSGI sunucusu altında çalıştırın.

İlk çağrı için, API'nizde `messages:read` kapsamına sahip bir makineden makineye uygulamasının istemci kimlik bilgileri akışıyla bir belirteç alın:

```sh
ACCESS_TOKEN=$(curl -s -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials \
  -d resource=https://api.example.com \
  -d scope=messages:read \
  https://your-environment.eu.keenauth.app/oauth2/token | jq -r .access_token)

curl -i http://localhost:8080/api/messages -H "Authorization: Bearer $ACCESS_TOKEN"
```

## 5. Ağ olmadan test edin

`keenauth.testing`, genel bir test anahtarıyla imzalanmış belirteçler üretir ve `testing.verifier` yalnızca bu belirteçleri kabul eder. Testler `get_protector` yerine bu doğrulayıcıyı kullanan bir süsleyici koyar; bu yüzden ağa, KeenAuth hesabına veya ayarlarda bir test anahtarına ihtiyaç duymazlar. Bkz. [Çevrimdışı test kiti](/docs/sdk/testing).

```python
class ProtectedAPITest(SimpleTestCase):
    def setUp(self) -> None:
        self.cache = MemoryRevocationCache()
        test_protector = Protector(testing.verifier(audience=AUDIENCE), public_base_url=BASE_URL, revocation=self.cache)
        patcher = mock.patch.object(auth, "get_protector", return_value=test_protector)
        patcher.start()
        self.addCleanup(patcher.stop)
```

```python
    def test_scope(self) -> None:
        self.assert_problem(self.get("/api/messages", testing.mint_access_token(AUDIENCE)), 403, "insufficient_scope",
                            'scope="messages:read"')
        tok = testing.mint_access_token(AUDIENCE, claims={"scope": "profile messages:read"})
        self.assertEqual(self.get("/api/messages", tok).status_code, 200)
```

```sh
set -a; . ./.env.example; set +a
DJANGO_SECRET_KEY=insecure-test-only python manage.py test
```

Örneğin testleri, geçerli bir belirtecin kabul edildiğini ve eksik, süresi dolmuş veya yanlış hedef kitleli bir belirtecin, eksik bir kapsamın, URL sorgusundaki bir belirtecin ve iptal edilmiş bir belirtecin aşağıdaki durum kodlarıyla reddedildiğini denetler. Ayrıca `PUBLIC_BASE_URL` için bir kanıtla DPoP bağlı bir belirteç gönderirler ve üretim doğrulayıcısının test sağlayıcısını reddettiğini kontrol ederler. Testler ayrıca imzalı `token.revoked` ttesliminatlarını da gönderir: geçerli bir teslimat belirteci iptal eder; sahte, değiştirilmiş veya eski bir teslimat `400` alır ve hiçbir şeyi iptal etmez; tekrarlanan bir teslimatın tek bir etkisi olur.

## Yanıtlar

| İstek | Durum | Gövdede `code` |
|---|---|---|
| `Authorization` başlığı yok | `401` | `missing_token` |
| Süresi dolmuş belirteç, başka bir API için belirteç, hatalı imza, iptal edilmiş belirteç | `401` | `invalid_token` |
| Gerekli kapsamı olmayan belirteç | `403` | `insufficient_scope` |
| URL sorgusunda belirteç (`?access_token=`) | `400` | `invalid_request` |

Gövde `application/problem+json` ve `WWW-Authenticate` başlığı standart sınamayı taşır; Django birden fazla sınamayı tek bir başlıkta birleştirir. Tam tablo [Ara katmanla API'nizi koruma](/docs/sdk/protect-api#yanıtlar) sayfasındadır.

## Proxy arkasında

İstemciler DPoP kullanıyorsa, ara katman her kanıtın istemcinin çağırdığı URL için üretildiğini denetler. `PUBLIC_BASE_URL` değerini, örneğin `https://api.example.com`, istemcilerin çağırdığı dış URL olarak ayarlayın; bu, bir proxy veya yük dengeleyici arkasında da geçerlidir. Bu ayar zorunludur ve `https` kullanmalıdır (`http` yalnızca `localhost`, `127.0.0.1` veya `[::1]` için); bu ayar olmadan uygulama başlamayı reddeder. Ara katman `X-Forwarded-*` başlıklarını kendisi asla okumaz; Django bunları yalnızca `SECURE_PROXY_SSL_HEADER` veya `USE_X_FORWARDED_HOST` ayarladığınızda uygular ve `PUBLIC_BASE_URL` ayarlıyken denetlenen URL bunlara bağlı değildir.

## İptal edilmiş belirteçler

Bir erişim belirteci süresi dolana kadar geçerli kalır; bu yüzden KeenAuth, belirteçlerin iptal edildiğini API'nize bildirir. Örnek, süsleyiciye bellek içi bir iptal önbelleği verir ve bunu besleyen bir `token.revoked` web kancasını `POST /webhooks/keenauth` (`api/webhook.py`) adresinde sunar.

1. Panelde, `https://<your host>/webhooks/keenauth` URL'siyle, `token.revoked` olaylarına abone olan bir web kancası uç noktası oluşturun ve imza sırrını `KEENAUTH_WEBHOOK_SECRET` olarak dışa aktarın.
2. İşleyici, imzayı SDK ile ham gövde üzerinde doğrular ve geçersizse ya da zaman damgası beş dakikadan fazla kaymışsa `400` yanıtı verir. Olayın seçicisini (`access_token`, `session`, `user`, `client`, `org`, `org_user`, `client_user`) bir iptal işaretçisine eşler ve `204` yanıtı verir; ara katman bunun ardından `not_before` öncesinde verilmiş eşleşen her belirteci reddeder.
3. KeenAuth en az bir kez teslim eder; bu yüzden işleyici her `webhook-id` değerini bir kez uygular. İşaretçi saklanamazsa `503` yanıtı verir ve KeenAuth olayı yeniden teslim eder. Diğer olay türlerini ve hiçbir erişim belirtecinin eşleşemeyeceği seçicileri (`refresh_family`, `api`) yok sayar.

İşleyici olay kimliğini, seçici türünü ve nedeni günlüğe yazar; gövdeyi, imzayı veya sırrı asla yazmaz. Her çalışan süreç kendi önbelleğine sahiptir; birden fazla çalışan veya örnek varsa bunun yerine paylaşılan bir depo kullanın. Bkz. [İptal önbelleği](/docs/sdk/protect-api#iptal-önbelleği-tokenrevoked).

## Sonraki adımlar

- [Ara katmanla API'nizi koruma](/docs/sdk/protect-api): Flask, adım artırma gereksinimleri, DPoP ve tüm seçenekler.
- [Belirteçleri doğrulama](/docs/sdk/token-verification): ara katmanın altındaki doğrulayıcı.
- [Çevrimdışı test kiti](/docs/sdk/testing).
