
# Django-Sitzungen

`keenauth.bff.django` ergänzt eine Django-4.2-Anwendung oder neuer um Anmeldung. Ihr Server ist der OAuth-Client und behält alle Token serverseitig. Der Browser erhält nur ein verschlüsseltes Session-Cookie mit dem Attribut HttpOnly und sieht niemals ein Access-, Refresh- oder ID-Token. Dies ist das Backend-for-Frontend-(BFF)-Muster. Es ist Teil von `keenauth-python`, das noch nicht auf PyPI veröffentlicht ist.

Um stattdessen eine API abzusichern, die Bearer-Token empfängt, lesen Sie [Ihre API schützen](/docs/sdk/protect-api).

## Installation

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

## Bevor Sie beginnen

1. Erstellen Sie im Dashboard in Ihrer Umgebung eine Webanwendung.
2. Wählen Sie die Client-Authentifizierung: `private_key_jwt` (empfohlen, `PrivateKeyJwt`) oder ein Client-Secret (`ClientSecretBasic`).
3. Hinterlegen Sie die Redirect-URI `https://app.acme.example/auth/callback` und die Redirect-URI nach der Abmeldung `https://app.acme.example/`.
4. Erzeugen Sie ein Cookie-Secret mit mindestens 32 Zeichen, beispielsweise mit `openssl rand -base64 32`. Bewahren Sie es in Ihrem Secret-Store auf, nicht im Repository.

## Instanz erstellen

```python
import os
from keenauth.bff import BffOptions, ClientSecretBasic, KeenAuthBff

bff = KeenAuthBff(BffOptions(
    issuer="https://acme.eu.keenauth.app",  # your environment's issuer, exactly
    client_id="app_01j9zzzzzzzzzzzzzzzzzzzz",
    client_auth=ClientSecretBasic(os.environ["KEENAUTH_CLIENT_SECRET"]),
    app_base_url="https://app.acme.example",
    secret=os.environ["KEENAUTH_COOKIE_SECRET"],
))
```

`app_base_url` ist die öffentliche Adresse Ihrer Anwendung. Das SDK erstellt daraus die Redirect-URI und alle Weiterleitungen, niemals aus dem Header `Host` oder den Headern `X-Forwarded-*` der Anfrage. Reines `http` wird nur für `localhost` akzeptiert. Die Optionen werden beim Erstellen von `KeenAuthBff` geprüft, sodass ein Konfigurationsfehler die Anwendung beim Start anhält.

## private_key_jwt verwenden

Bei `private_key_jwt` besitzt die Anwendung kein gemeinsames Geheimnis. Übergeben Sie Ihren privaten Schlüssel an `PrivateKeyJwt`. Das SDK signiert dann für jede Anfrage an den Token-Endpunkt eine neue Client-Assertion (RFC 7523):

```python
from keenauth.bff import PrivateKeyJwt

client_auth = PrivateKeyJwt(key=os.environ["KEENAUTH_PRIVATE_KEY"])  # unencrypted PEM
```

- Unterstützte Schlüssel: EC P-256 (`ES256`), EC P-384 (`ES384`), Ed25519 (`EdDSA`) und RSA mit mindestens 2048 Bit (standardmäßig `PS256`; übergeben Sie `algorithm="RS256"` für `RS256`).
- Jede Assertion hat `iss` und `sub` mit Ihrer Client-ID, `aud` mit dem Issuer, einen zufälligen `jti` und eine Gültigkeit von 60 Sekunden. Ihre `kid` ist der RFC-7638-Thumbprint des öffentlichen Schlüssels.
- Hinterlegen Sie den öffentlichen Schlüssel an der Anwendung. `keenauth.m2m.load_private_key(pem).public_jwk()` gibt ihn als JWK zurück.

Um mit Ihrem eigenen Schlüsseldienst (KMS oder HSM) zu signieren, übergeben Sie stattdessen eine Funktion: `PrivateKeyJwt(assertion=sign)`. `sign()` muss bei jedem Aufruf eine neue signierte Assertion zurückgeben.

## Middleware hinzufügen

```python
# settings.py
KEENAUTH_BFF = "myproject.auth.bff"  # dotted path to the KeenAuthBff instance
MIDDLEWARE = [
    # ...
    "keenauth.bff.django.KeenAuthMiddleware",
]
```

Die Middleware stellt die unten aufgeführten Routen bereit und liest bei jeder anderen Anfrage die Sitzung in `request.keenauth`.

```python
# views.py
from django.http import HttpResponse
from keenauth.bff.django import require_auth

@require_auth()
def orders(request):
    return HttpResponse(f"Signed in as {request.keenauth.session.user.email}")
```

## Routen

| Route | Funktion |
|---|---|
| `GET /auth/login` | Startet die Anmeldung. Optionale Query-Parameter: `returnTo` (wohin die Person gelangt) und `organization` (Anmeldung in einer Organisation). |
| `GET /auth/callback` | Schließt die Anmeldung ab und setzt das Session-Cookie. |
| `GET` oder `POST /auth/logout` | Meldet bei KeenAuth ab und kehrt zu Ihrer Anwendung zurück. |

Die Anmeldung verwendet den Authorization-Code-Flow mit PKCE (S256). Die Anfrage wird an den Server übermittelt (PAR), sofern Ihre Umgebung dies unterstützt. Beim Callback prüft das SDK `state`, `iss` und das ID-Token einschließlich seines `nonce`. Jede erfolgreiche Anmeldung erzeugt eine neue Sitzung; die alte wird verworfen.

`returnTo` muss ein Pfad Ihrer eigenen Anwendung sein, etwa `/orders?tab=open`. Jeder andere Wert, etwa `//evil.example` oder eine vollständige URL, wird ignoriert, und die Person gelangt zu `/`.

## Geschützte Views

Versehen Sie eine View mit `@require_auth()`.


Ohne Sitzung:

- Eine Seitenanfrage (`GET` oder `HEAD`) wird mit `returnTo` auf `/auth/login` umgeleitet;
- eine Anfrage an `/api/…` oder mit einer anderen Methode erhält `401` mit einem Problem-Dokument. Übergeben Sie `api=True`, um auf jedem Pfad mit `401` zu antworten.

Für `POST`, `PUT`, `PATCH`, `DELETE` und andere unsichere Methoden muss der Browser angeben, dass die Anfrage von Ihrer Anwendung stammt: `Sec-Fetch-Site: same-origin` oder ein Header `Origin`, der dem Origin Ihrer Anwendung entspricht. Andere Anfragen erhalten `403`. Dies schützt zusätzlich zu `SameSite=Lax` vor Cross-Site-Request-Forgery.

## Sitzung nutzen und Ihre API aufrufen

Der Sitzungskontext enthält:

- `session`: `None` oder die angemeldete Person. `session.user` stellt typisierte Claims des ID-Tokens bereit, etwa `subject`, `email`, `name`, `org_id` und `org_roles`; alle Claims stehen in `session.user.claims`. Das Sitzungsobjekt enthält niemals ein Token.
- `access_token`: ein gültiges Access-Token für serverseitige Aufrufe oder `None`. Senden Sie es nicht an den Browser.

Läuft das Access-Token innerhalb von 60 Sekunden ab, erneuert das SDK es, bevor Ihre View ausgeführt wird. Jede Erneuerung rotiert das Refresh-Token und setzt das neue Session-Cookie in der Antwort. Verweigert der Server das Refresh-Token, endet die Sitzung. Ein vorübergehender Serverfehler lässt die Sitzung unverändert. Gleichzeitige Anfragen in einem Prozess teilen sich eine Erneuerung.

## Organisation wechseln

`bff.switch_organization_path("org_01j9…", "/dashboard")` gibt einen Link zu `/auth/login` zurück, der die Person erneut in dieser Organisation anmeldet. KeenAuth prüft die Mitgliedschaft. Nach der Anmeldung enthält `session.user.org_id` die Organisation.

## Abmelden

Senden Sie aus einem Formular ein `POST` an `/auth/logout`. Das SDK löscht das Session-Cookie (und die gespeicherte Sitzung, falls Sie einen Store verwenden), widerruft das Refresh-Token und leitet den Browser zum Logout-Endpunkt Ihrer Umgebung weiter, der ihn zu Ihrer Anwendung zurückführt. Eine Anfrage, die der Browser als von einer anderen Website stammend kennzeichnet, wird abgelehnt. Browser, die kein `Sec-Fetch-Site` senden, können sich nur über ein `POST` von Ihren eigenen Seiten abmelden.

## Session-Speicherung

Standardmäßig liegt die gesamte Sitzung im Cookie, verschlüsselt und mit AES-256-GCM authentifiziert, mit einem aus `secret` abgeleiteten Schlüssel. Ein geändertes oder kopiertes Cookie wird abgelehnt. Das Cookie heißt `__Host-keenauth_session.0` und wird mit `Secure`, `HttpOnly`, `SameSite=Lax` und `Path=/` gesetzt. Auf `http://localhost` entfallen das Präfix `__Host-` und `Secure`. Große Sitzungen werden auf bis zu drei Cookies aufgeteilt.

Wenn die Token auf dem Server bleiben sollen, übergeben Sie `store=` mit einem Objekt, das `get(id)`, `set(id, session, ttl_seconds)` und `delete(id)` bereitstellt, etwa mit Redis als Backend. Das Cookie enthält dann nur eine verschlüsselte zufällige Sitzungs-ID. Jede Anmeldung erzeugt eine neue ID und löscht den vorherigen Datensatz. Der Store enthält Token, beschränken Sie daher den Zugriff darauf. `MemorySessionStore` ist nur für Entwicklung und Tests gedacht.

Eine Sitzung dauert höchstens 7 Tage (`session_max_age`); eine Erneuerung verlängert sie nicht. Um das Secret zu ändern, übergeben Sie eine Liste: `secret=[new_secret, old_secret]`. Entfernen Sie das alte nach `session_max_age`.

## Aktuelle Einschränkungen

- DPoP-gebundene Token und Back-Channel-Logout werden noch nicht unterstützt.
- Erneuerungen werden nur innerhalb eines Serverprozesses geteilt. Bei mehreren Prozessen lässt die Anwendungseinstellung `refresh_reuse_interval_s` (Standard 5 Sekunden) eine parallele Erneuerung erfolgreich sein; setzen Sie sie auf `0`, beendet die zweite Erneuerung die Sitzung.
