Sessioni Django
keenauth.bff.django aggiunge l'accesso a un'applicazione Django 4.2 o successiva. Il tuo server è il client OAuth e conserva tutti i token lato server. Il browser riceve solo un cookie di sessione cifrato e HttpOnly, e non vede mai un access token, un refresh token o un ID token. Questo è il pattern backend-for-frontend (BFF). Fa parte di keenauth-python, che non è ancora pubblicato su PyPI.
Per proteggere invece un'API che riceve bearer token, consulta Proteggere la tua API.
Installazione
pip install "keenauth-python[django]"
Prima di iniziare
- Nella dashboard, crea un'applicazione web nel tuo ambiente.
- Scegli l'autenticazione del client:
private_key_jwt(consigliata,PrivateKeyJwt) oppure un client secret (ClientSecretBasic). - Aggiungi il redirect URI
https://app.acme.example/auth/callbacke il post-logout redirect URIhttps://app.acme.example/. - Genera un cookie secret di almeno 32 caratteri, per esempio con
openssl rand -base64 32. Conservalo nel tuo secret store, non nel repository.
Crea l'istanza
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 è l'indirizzo pubblico della tua app. L'SDK costruisce da lì il redirect URI e tutti i redirect, mai dagli header Host o X-Forwarded-* della richiesta. Il protocollo http è accettato solo per localhost. Le opzioni vengono verificate quando viene creato KeenAuthBff, quindi un errore di configurazione blocca l'app all'avvio.
Usa private_key_jwt
Con private_key_jwt l'applicazione non ha un segreto condiviso. Passa a PrivateKeyJwt la tua chiave privata: l'SDK firma una nuova client assertion (RFC 7523) per ogni richiesta all'endpoint dei token:
from keenauth.bff import PrivateKeyJwt
client_auth = PrivateKeyJwt(key=os.environ["KEENAUTH_PRIVATE_KEY"]) # unencrypted PEM
- Chiavi supportate: EC P-256 (
ES256), EC P-384 (ES384), Ed25519 (EdDSA) e RSA di almeno 2048 bit (PS256per impostazione predefinita; passaalgorithm="RS256"perRS256). - Ogni assertion ha
issesubimpostati al tuo client ID,audimpostato all'issuer, unjticasuale e una durata di 60 secondi. Il suokidè il thumbprint RFC 7638 della chiave pubblica. - Registra la chiave pubblica sull'applicazione.
keenauth.m2m.load_private_key(pem).public_jwk()la restituisce come JWK.
Per firmare con un tuo servizio di chiavi (un KMS o un HSM), passa invece una funzione: PrivateKeyJwt(assertion=sign). sign() deve restituire una nuova assertion firmata a ogni chiamata.
Aggiungi il middleware
# settings.py
KEENAUTH_BFF = "myproject.auth.bff" # dotted path to the KeenAuthBff instance
MIDDLEWARE = [
# ...
"keenauth.bff.django.KeenAuthMiddleware",
]
Il middleware serve le route qui sotto e legge la sessione in ogni altra richiesta, inserendola in request.keenauth.
# 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}")
Route
| Route | Cosa fa |
|---|---|
GET /auth/login |
Avvia l'accesso. Parametri query opzionali: returnTo (dove atterra l'utente) e organization (accedi a un'organizzazione). |
GET /auth/callback |
Completa l'accesso e imposta il cookie di sessione. |
GET o POST /auth/logout |
Disconnette in KeenAuth e torna alla tua app. |
L'accesso usa il flusso authorization code con PKCE (S256). La richiesta viene inviata in anticipo al server (PAR) quando il tuo ambiente lo supporta. Al callback l'SDK verifica state, iss e l'ID token, incluso il suo nonce. Ogni accesso riuscito crea una nuova sessione; quella precedente viene scartata.
returnTo deve essere un percorso della tua app, come /orders?tab=open. Qualsiasi altro valore, come //evil.example o un URL completo, viene ignorato e l'utente atterra su /.
Viste protette
Decora una vista con @require_auth().
Senza una sessione:
- una richiesta di pagina (
GEToHEAD) viene reindirizzata a/auth/loginconreturnTo; - una richiesta a
/api/…, oppure con un altro metodo, riceve401con un documento problem. Passaapi=Trueper rispondere401su qualsiasi percorso.
Per POST, PUT, PATCH, DELETE e gli altri metodi non sicuri, il browser deve indicare che la richiesta proviene dalla tua app: Sec-Fetch-Site: same-origin, oppure un header Origin uguale all'origine della tua app. Le altre richieste ricevono 403. Questo protegge dal cross-site request forgery in aggiunta a SameSite=Lax.
Usa la sessione e chiama la tua API
Il contesto della sessione contiene:
session:None, oppure l'utente autenticato.session.userha claim tipizzati dell'ID token comesubject,email,name,org_ideorg_roles; tutti i claim sono insession.user.claims. L'oggetto sessione non contiene mai un token.access_token: un access token valido per chiamate lato server, oppureNone. Non inviarlo al browser.
Quando l'access token scade entro 60 secondi, l'SDK lo rinnova prima che la tua vista venga eseguita. Ogni rinnovo ruota il refresh token e imposta il nuovo cookie di sessione sulla risposta. Se il server rifiuta il refresh token, la sessione termina. Un errore temporaneo del server lascia la sessione invariata. Le richieste concorrenti in un processo condividono un solo rinnovo.
Cambia organizzazione
bff.switch_organization_path("org_01j9…", "/dashboard") restituisce un link a /auth/login che fa accedere di nuovo l'utente in quell'organizzazione. KeenAuth verifica l'appartenenza. Dopo l'accesso, session.user.org_id contiene l'organizzazione.
Disconnessione
Invia una POST a /auth/logout da un form. L'SDK elimina il cookie di sessione (e la sessione memorizzata, se usi uno store), revoca il refresh token e manda il browser all'endpoint di logout del tuo ambiente, che lo riporta alla tua app. Una richiesta che il browser contrassegna come proveniente da un altro sito viene rifiutata. I browser che non inviano Sec-Fetch-Site possono disconnettere solo con una POST dalle tue pagine.
Archiviazione della sessione
Per impostazione predefinita l'intera sessione è nel cookie, cifrata e autenticata con AES-256-GCM usando una chiave derivata da secret. Un cookie modificato o copiato viene rifiutato. Il cookie si chiama __Host-keenauth_session.0 ed è impostato con Secure, HttpOnly, SameSite=Lax e Path=/. Su http://localhost il prefisso __Host- e Secure vengono omessi. Le sessioni grandi vengono suddivise in fino a tre cookie.
Per mantenere i token sul server, passa store= con un oggetto che abbia get(id), set(id, session, ttl_seconds) e delete(id), per esempio basato su Redis. Il cookie contiene allora solo un ID di sessione casuale cifrato. Ogni accesso crea un nuovo ID ed elimina il record precedente. Lo store contiene token, quindi limitane l'accesso. MemorySessionStore è pensato solo per sviluppo e test.
Una sessione dura al massimo 7 giorni (session_max_age); il rinnovo non la estende. Per cambiare il secret, passa una lista: secret=[new_secret, old_secret]. Rimuovi quello vecchio dopo session_max_age.
Limiti attuali
- I token vincolati con DPoP e il back-channel logout non sono ancora gestiti.
- I rinnovi sono condivisi solo all'interno di un singolo processo del server. Con più processi, l'impostazione dell'applicazione
refresh_reuse_interval_s(predefinita 5 secondi) permette a un rinnovo parallelo di riuscire; se la imposti a0, il secondo rinnovo termina la sessione.