Sessions FastAPI
keenauth.bff.fastapi ajoute la connexion à une application FastAPI. Votre serveur est le client OAuth et conserve tous les jetons côté serveur. Le navigateur ne reçoit qu'un cookie de session chiffré, HttpOnly, et ne voit jamais de jeton d'accès, de jeton de rafraîchissement ou de jeton d'identité. Il s'agit du motif backend-for-frontend (BFF). Il fait partie de keenauth-python, qui n'est pas encore publié sur PyPI.
Pour protéger une API qui reçoit à la place des jetons porteurs, consultez Protéger votre API.
Installation
pip install "keenauth-python[fastapi]"
Avant de commencer
- Dans le tableau de bord, créez une application web dans votre environnement.
- Choisissez l'authentification du client :
private_key_jwt(recommandé,PrivateKeyJwt) ou un secret client (ClientSecretBasic). - Ajoutez l'URI de redirection
https://app.acme.example/auth/callbacket l'URI de redirection après déconnexionhttps://app.acme.example/. - Générez un secret de cookie d'au moins 32 caractères, par exemple avec
openssl rand -base64 32. Conservez-le dans votre gestionnaire de secrets, et non dans le dépôt.
Créer l'instance
import os
from keenauth.bff import BffOptions, ClientSecretBasic, KeenAuthBff
bff = KeenAuthBff(BffOptions(
issuer="https://acme.eu.keenauth.app", # l'émetteur de votre environnement, exactement
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 est l'adresse publique de votre application. Le SDK construit l'URI de redirection et toutes les redirections à partir de celle-ci, jamais à partir des en-têtes Host ou X-Forwarded-* de la requête. Le protocole http n'est accepté que pour localhost. Les options sont vérifiées à la création de KeenAuthBff, de sorte qu'une erreur de configuration arrête l'application au démarrage.
Utiliser private_key_jwt
Avec private_key_jwt, l'application ne dispose d'aucun secret partagé. Transmettez votre clé privée à PrivateKeyJwt : le SDK signe une nouvelle assertion client (RFC 7523) pour chaque requête vers le point de terminaison de jetons :
from keenauth.bff import PrivateKeyJwt
client_auth = PrivateKeyJwt(key=os.environ["KEENAUTH_PRIVATE_KEY"]) # PEM non chiffré
- Clés prises en charge : EC P-256 (
ES256), EC P-384 (ES384), Ed25519 (EdDSA) et RSA d'au moins 2048 bits (PS256par défaut ; passezalgorithm="RS256"pourRS256). - Chaque assertion a
issetsubdéfinis sur votre identifiant client,auddéfini sur l'émetteur, unjtialéatoire et une durée de vie de 60 secondes. Sonkidest l'empreinte RFC 7638 de la clé publique. - Enregistrez la clé publique sur l'application.
keenauth.m2m.load_private_key(pem).public_jwk()la renvoie sous forme de JWK.
Pour signer avec votre propre service de clés (un KMS ou un HSM), transmettez une fonction à la place : PrivateKeyJwt(assertion=sign). sign() doit renvoyer une nouvelle assertion signée à chaque appel.
Installer le middleware
from fastapi import Depends, FastAPI
from keenauth.bff import SessionContext
from keenauth.bff.fastapi import RequireSession, install
app = FastAPI()
install(app, bff)
@app.get("/orders")
def orders(ctx: SessionContext = Depends(RequireSession(bff))):
return {"email": ctx.session.user.email}
install expose les routes ci-dessous et lit la session à chaque autre requête pour la placer dans request.state.keenauth. Les appels réseau du SDK sont synchrones ; install les exécute dans le pool de threads de Starlette afin de ne pas bloquer la boucle d'événements.
Routes
| Route | Ce qu'elle fait |
|---|---|
GET /auth/login |
Démarre la connexion. Paramètres de requête facultatifs : returnTo (où arrive l'utilisateur) et organization (se connecter à une organisation). |
GET /auth/callback |
Termine la connexion et définit le cookie de session. |
GET ou POST /auth/logout |
Déconnecte l'utilisateur chez KeenAuth et le renvoie vers votre application. |
La connexion utilise le flux à code d'autorisation avec PKCE (S256). La requête est transmise au serveur (PAR) lorsque votre environnement le propose. Au retour de l'appel, le SDK vérifie state, iss et le jeton d'identité, y compris son nonce. Chaque connexion réussie crée une nouvelle session ; l'ancienne est supprimée.
returnTo doit être un chemin de votre propre application, tel que /orders?tab=open. Toute autre valeur, comme //evil.example ou une URL complète, est ignorée et l'utilisateur arrive sur /.
Vues protégées
Ajoutez Depends(RequireSession(bff)) à une opération de chemin. Elle renvoie le contexte de session.
Sans session :
- une requête de page (
GETouHEAD) est redirigée vers/auth/loginavecreturnTo; - une requête vers
/api/…, ou utilisant une autre méthode, reçoit401avec un document de problème. Passezapi=Truepour répondre401sur tout chemin.
Pour POST, PUT, PATCH, DELETE et les autres méthodes non sûres, le navigateur doit indiquer que la requête provient de votre application : Sec-Fetch-Site: same-origin, ou un en-tête Origin égal à l'origine de votre application. Les autres requêtes reçoivent 403. Cela protège contre la falsification de requête intersite, en complément de SameSite=Lax.
Utiliser la session et appeler votre API
Le contexte de session contient :
session:None, ou l'utilisateur connecté.session.userexpose des revendications typées du jeton d'identité, commesubject,email,name,org_idetorg_roles; toutes les revendications figurent danssession.user.claims. L'objet de session ne contient jamais de jeton.access_token: un jeton d'accès valide pour les appels côté serveur, ouNone. Ne l'envoyez pas au navigateur.
Lorsque le jeton d'accès expire dans moins de 60 secondes, le SDK le rafraîchit avant l'exécution de votre vue. Chaque rafraîchissement renouvelle le jeton de rafraîchissement et définit le nouveau cookie de session dans la réponse. Si le serveur refuse le jeton de rafraîchissement, la session prend fin. Une erreur serveur temporaire laisse la session inchangée. Les requêtes simultanées d'un même processus partagent un seul rafraîchissement.
Changer d'organisation
bff.switch_organization_path("org_01j9…", "/dashboard") renvoie un lien vers /auth/login qui reconnecte l'utilisateur dans cette organisation. KeenAuth vérifie l'appartenance. Après la connexion, session.user.org_id contient l'organisation.
Se déconnecter
Envoyez une requête POST vers /auth/logout depuis un formulaire. Le SDK supprime le cookie de session (et la session stockée, si vous utilisez un store), révoque le jeton de rafraîchissement et envoie le navigateur vers le point de terminaison de déconnexion de votre environnement, qui le renvoie vers votre application. Une requête que le navigateur marque comme provenant d'un autre site est refusée. Les navigateurs qui n'envoient pas Sec-Fetch-Site ne peuvent se déconnecter que par un POST depuis vos propres pages.
Stockage de session
Par défaut, la session entière se trouve dans le cookie, chiffrée et authentifiée avec AES-256-GCM à l'aide d'une clé dérivée de secret. Un cookie modifié ou copié est rejeté. Le cookie se nomme __Host-keenauth_session.0 et est défini avec Secure, HttpOnly, SameSite=Lax et Path=/. Sur http://localhost, le préfixe __Host- et Secure sont omis. Les sessions volumineuses sont réparties sur jusqu'à trois cookies.
Pour conserver les jetons côté serveur, passez store= avec un objet qui possède get(id), set(id, session, ttl_seconds) et delete(id), par exemple adossé à Redis. Le cookie ne contient alors qu'un identifiant de session aléatoire chiffré. Chaque connexion crée un nouvel identifiant et supprime l'enregistrement précédent. Le store contient des jetons : restreignez son accès. MemorySessionStore est réservé au développement et aux tests.
Une session dure au maximum 7 jours (session_max_age) ; le rafraîchissement ne la prolonge pas. Pour changer le secret, passez une liste : secret=[new_secret, old_secret]. Retirez l'ancien après session_max_age.
Limites actuelles
- Les jetons liés à DPoP et la déconnexion back-channel ne sont pas encore gérés.
- Les rafraîchissements ne sont partagés qu'au sein d'un même processus serveur. Avec plusieurs processus, le paramètre d'application
refresh_reuse_interval_s(5 secondes par défaut) permet à un rafraîchissement parallèle de réussir ; si vous le réglez sur0, le second rafraîchissement met fin à la session.