Aller au contenu
English

Cycle de vie d'une session

  • Leçon 9
  • 55 min
  • Module 3 : Sessions et intégration à l'application

Ce que vous saurez faire

Décidez quand une session de navigateur se termine et testez chaque fin : inactivité, limite absolue, renouvellement refusé, déconnexion et deuxième personne dans le même navigateur. Une session qui se termine trop tard conserve un accès perdu. Une déconnexion incomplète donne les données de la personne précédente à la suivante.

Avant de commencerCe qu’il faut savoir et avoir sous la mainEnviron 55 minutes

Ce que vous devez savoir

Ce qu’il vous faut sur votre ordinateur

  • Le projet après Choix de session dans le navigateur : python -m pytest affiche 193 passed (193 tests réussis).
  • Le fournisseur d’exercice (python -m idp) et l’API (fastapi dev) en cours d’exécution, chacun dans son terminal.
  • Un troisième terminal dans le dossier du projet, avec l’environnement virtuel actif, pour curl et psql.

Testé sur macOS (Apple silicon) avec Chromium 156; les commandes Windows sont écrites, mais non testées. Certaines étapes modifient les heures dans la table sessions à la main pour éviter d’attendre 30 minutes ou 8 heures. Ces étapes sont indiquées comme construites.

Une bibliothèque publique met quelques ordinateurs à la disposition de tous. Vous vous asseyez, vous vous connectez à votre courriel et vous lisez un moment. Puis un ami appelle, et vous partez pendant vingt minutes. Certains ordinateurs de la bibliothèque vous déconnectent après une période d’inactivité. Tous déconnectent tout le monde à l’heure de fermeture. Ce sont deux horloges : une pour l’inactivité, une pour la journée entière.

Il y a un troisième piège. Avant de partir, vous fermez votre courriel et cliquez sur « Sign out » (se déconnecter). La personne suivante ouvre le même site de courriel et clique sur « Sign in » (se connecter). Le site ne demande pas de mot de passe : il la connecte à votre compte. Votre logiciel de courriel vous a déconnecté, mais le service de compte derrière lui se souvenait encore de vous. La personne suivante lit maintenant vos courriels.

Ainsi, « déconnecté » a plusieurs sens. Pour terminer complètement une session, vous devez connaître chaque endroit qui se souvient de vous.

Le problème : une session qui se termine trop tard, ou pas complètement

Section intitulée « Le problème : une session qui se termine trop tard, ou pas complètement »

Les sessions de l’API de billets de soutien de la dernière leçon durent jusqu’à 8 heures. Trois questions montrent le problème.

  • Sam laisse le centre de soutien ouvert et rentre chez lui à midi. Le témoin fonctionne jusqu’au soir. Toute personne au bureau de Sam peut l’utiliser.
  • Larkfield désactive le compte d’Omar chez le fournisseur d’identité. L’API conserve sa propre session pour Omar. Combien de temps le navigateur ouvert d’Omar continue-t-il de fonctionner?
  • Sam se déconnecte, et Ines s’assoit au même bureau. Elle clique sur « Sign in ». Pour qui l’API la prend-elle?

La troisième question a une réponse mesurée, et c’est la mauvaise. L’auteur a connecté Sam, terminé sa session uniquement dans l’API, puis s’est connecté « comme Ines » dans le même navigateur. Le fournisseur n’a affiché aucune page, et /v1/me a répondu "user_id": "usr-sam". La même chose s’est produite dans Chromium :

La page du centre de soutien avec la ligne d'état : Signed in as usr-sam (connecté comme usr-sam). La session se termine au plus tard à 2026-10-10T14:32:49, ou après 30 minutes sans requête. Les boutons Switch account (changer de compte) et Sign out (se déconnecter) sont affichés.
Construit : la session de Sam a été terminée uniquement dans l'API. Puis la personne suivante a cliqué sur Sign in, et le fournisseur a reconnecté Sam sans afficher de page. L’image est en anglais.

Cette leçon donne à chaque question une réponse que le code applique et qu’un test prouve.

Une session a besoin de trois horloges. Chacune répond à un risque différent.

  • Délai d’inactivité : la session se termine après une période sans requête. C’est la règle d’inactivité de la bibliothèque. Elle protège l’onglet oublié. Le projet utilise 30 minutes (SESSION_IDLE_MINUTES).
  • Délai absolu : la session se termine à un moment fixe après la connexion, quoi qu’il arrive. C’est l’heure de fermeture. Il impose une nouvelle connexion chaque jour de travail. Le projet utilise 8 heures (SESSION_MAX_HOURS).
  • Durée d’accès : le jeton d’accès dans la session dure 10 minutes. Ensuite, l’API interroge de nouveau le fournisseur avec le jeton de renouvellement. Si le fournisseur refuse, la session se termine. Ainsi, une décision du fournisseur atteint l’API en quelques minutes.

Chaque fin inscrit sa raison dans la ligne de la session, dans end_reason : idle_timeout, absolute_timeout, provider_refused, ou logout pour une déconnexion. La prochaine requête avec ce témoin reçoit 401 session_ended (session terminée), avec la raison dans le message. L’API supprime le témoin. Voici toute la vie d’une session :

Un diagramme d'états avec quatre états. Déconnecté, aucun témoin. GET /auth/login mène à Connexion commencée : state, nonce et verifier sont conservés pendant 10 minutes, pour une seule utilisation. Une flèche pointillée revient à Déconnecté : un state incorrect donne 400 login_failed. Le retour OK mène à Active : témoin défini, durée d'accès de 10 minutes, inactivité de 30 minutes, limite absolue de 8 heures. Active a une boucle vers elle-même : une requête, ou un renouvellement que le fournisseur autorise. Quatre fins mènent d'Active à Terminée : logout, idle_timeout, absolute_timeout, provider_refused. Terminée signifie que end_reason est défini et que le témoin est supprimé. Une flèche pointillée va de Terminée à Déconnecté : la prochaine requête reçoit 401 session_ended.
Le cycle de vie d'une session tel que l'API l'applique. Chaque flèche est un test dans tests/test_sessions.py, et chaque raison de fin est une valeur de sessions.end_reason.
Description textuelle du diagramme

Commencez à gauche. Une connexion attend d’abord dans login_requests pendant au plus 10 minutes, et son state fonctionne une seule fois. Un retour valide rend la session active. Tant qu’elle est active, chaque requête déplace l’horloge d’inactivité, et un renouvellement prolonge la durée d’accès. Quatre flèches quittent l’état actif, une par raison de fin. Une session terminée ne redevient jamais active : il faut une nouvelle connexion, avec un nouveau témoin.

Voici une matinée construite selon les règles du code. Sam se connecte à 9 h. Trois horloges démarrent : accès jusqu’à 9 h 10, fin d’inactivité à 9 h 30, fin absolue à 17 h.

Heure Requête de Sam Ce que fait l’API Accès jusqu’à Fin d’inactivité
9 h se connecte nouvelle session, témoin défini 9 h 10 9 h 30
9 h 04 GET /v1/me 200; l’horloge d’inactivité se déplace 9 h 10 9 h 34
9 h 25 GET /v1/me durée d’accès écoulée : renouvellement auprès du fournisseur; 200 9 h 35 9 h 55
10 h 10 GET /v1/me 45 minutes sans requête : 401 session_ended (idle_timeout) — —
Une chronologie de 9 h à 10 h 20 avec quatre lignes. Requêtes de Sam : marques vertes à 9 h (connexion), 9 h 04 et 9 h 25, puis marque rouge à 10 h 10 avec 401 idle_timeout. Durée d'accès, 10 minutes : un rectangle jusqu'à 9 h 10, puis un rectangle pointillé expiré (aucune requête), puis un rectangle vert renouvelé de 9 h 25 à 9 h 35; le serveur a utilisé le jeton de renouvellement à 9 h 25. Limite d'inactivité, 30 minutes après la dernière requête : trois lignes bleues finissent à 9 h 30, 9 h 34 et 9 h 55, puis un rectangle rouge à 10 h 10 : aucune requête pendant 45 minutes. Limite absolue, 8 heures après la connexion : une flèche vers la droite finit à 17 h, quoi qu'il arrive.
Construit : les trois horloges d'une session. La fin d'inactivité se déplace à chaque requête; la durée d'accès est renouvelée seulement quand une requête constate son expiration; la fin absolue ne change jamais.
Description textuelle du diagramme

Lisez les lignes de haut en bas. Entre 9 h 10 et 9 h 25, la durée d’accès est écoulée, mais rien ne se passe. L’API renouvelle seulement quand une requête en a besoin. À 9 h 25, elle renouvelle avant de répondre. La ligne d’inactivité recommence à chaque requête. Sa fin passe donc de 9 h 30 à 9 h 34, puis à 9 h 55. À 10 h 10, la vérification d’inactivité passe en premier, avant tout renouvellement. La session se termine donc sans appel au fournisseur.

Remarquez l’ordre à 10 h 10. L’API vérifie d’abord la fin absolue, puis la fin d’inactivité, et seulement ensuite la durée d’accès. Une session terminée n’atteint jamais le fournisseur.

Grace, propriétaire de Larkfield, demande des preuves, pas des promesses : « Montrez-moi chaque façon dont une session se termine. » Les tests le font déjà. Exécutez-les d’abord :

Fenêtre de terminal
python -m pytest tests/test_sessions.py -v
tests/test_sessions.py::test_sign_in_sets_an_httponly_cookie_and_the_api_knows_the_person PASSED
tests/test_sessions.py::test_the_server_keeps_only_a_hash_of_the_cookie_and_an_encrypted_refresh_token PASSED
tests/test_sessions.py::test_a_callback_with_an_unknown_or_used_state_fails PASSED
tests/test_sessions.py::test_return_to_cannot_send_you_to_another_site PASSED
tests/test_sessions.py::test_a_change_with_the_cookie_needs_the_csrf_token PASSED
tests/test_sessions.py::test_logout_ends_the_session_and_revokes_the_refresh_token PASSED
tests/test_sessions.py::test_a_session_ends_when_idle_or_too_old[last_seen_at = now() - interval '31 minutes'-idle_timeout] PASSED
tests/test_sessions.py::test_a_session_ends_when_idle_or_too_old[expires_at = now() - interval '1 second'-absolute_timeout] PASSED
tests/test_sessions.py::test_the_session_asks_the_provider_again_when_the_access_token_is_old PASSED
tests/test_sessions.py::test_a_disabled_account_loses_the_session_at_the_next_refresh PASSED
tests/test_sessions.py::test_account_switching_starts_a_new_session_for_the_other_person PASSED
tests/test_sessions.py::test_the_page_and_the_session_answer_are_not_cached PASSED

Les tests déplacent les heures dans la base de données au lieu d’attendre. Faites maintenant la même chose à la main, avec l’API en cours d’exécution, pour voir chaque réponse.

  1. Dans le troisième terminal, définissez deux petites fonctions utilitaires. signin suit la connexion comme le navigateur, avec le fichier de témoins jar.txt. Elle ouvre d’abord la page du fournisseur; elle choisit la personne seulement si la page le demande. me appelle /v1/me avec ce fichier :

    Fenêtre de terminal
    signin() {
    TO=$(curl -s -o /dev/null -w '%{redirect_url}' -b jar.txt -c jar.txt "http://127.0.0.1:8000/auth/login?return_to=/app/$2")
    BACK=$(curl -s -o /dev/null -w '%{redirect_url}' -b jar.txt -c jar.txt "$TO") # open the provider's page
    # no redirect: the page asks for a person, so pick one
    [ -z "$BACK" ] && BACK=$(curl -s -o /dev/null -w '%{redirect_url}' -b jar.txt -c jar.txt -d user="$1" "$TO")
    curl -s -o /dev/null -b jar.txt -c jar.txt "$BACK"
    curl -s -b jar.txt http://127.0.0.1:8000/auth/session; echo
    }
    me() { curl -s -b jar.txt http://127.0.0.1:8000/v1/me; echo; }

    Commencez avec un navigateur neuf : supprimez jar.txt (rm -f jar.txt, ou Remove-Item jar.txt dans PowerShell). Puis exécutez signin usr-sam. La réponse commence par {"signed_in":true,"user_id":"usr-sam".

  2. Renouvellement (construit). Placez la fin de la durée d’accès de Sam une minute dans le passé, puis appelez l’API :

    Fenêtre de terminal
    docker compose exec db psql -U tickets -c "UPDATE sessions SET access_expires_at = now() - interval '1 minute' WHERE user_id = 'usr-sam' AND ended_at IS NULL;"
    me
    docker compose exec db psql -U tickets -c "SELECT user_id, access_expires_at - now() AS access_left FROM sessions WHERE user_id = 'usr-sam' AND ended_at IS NULL;"
    UPDATE 1
    {"user_id":"usr-sam","kind":"user","name":"Sam", … }
    user_id | access_left
    ---------+-----------------
    usr-sam | 00:09:59.687126
    (1 row)

    /v1/me a répondu 200, et la durée d’accès est de nouveau de 10 minutes. L’API a utilisé le jeton de renouvellement auprès du fournisseur avant de répondre. Le fournisseur a aussi remplacé le jeton de renouvellement : la ligne conserve maintenant le nouveau, chiffré.

  3. Compte désactivé chez le fournisseur. Connectez-vous comme Omar, puis désactivez son compte chez le fournisseur :

    Fenêtre de terminal
    rm -f jar.txt
    signin usr-omar
    python -m idp disable usr-omar
    me
    usr-omar is disabled: they cannot sign in or refresh tokens. Tokens already issued stay valid until they expire.
    {"user_id":"usr-omar", … }

    Omar reçoit encore 200. La durée d’accès de sa session n’est pas écoulée, donc l’API n’interroge pas encore le fournisseur. Déplacez maintenant sa fin d’accès dans le passé (construit), comme à l’étape 2, et appelez de nouveau :

    Fenêtre de terminal
    docker compose exec db psql -U tickets -c "UPDATE sessions SET access_expires_at = now() - interval '1 second' WHERE user_id = 'usr-omar' AND ended_at IS NULL;"
    me
    python -m idp enable usr-omar
    {"error":{"code":"session_ended","message":"Your session has ended (provider_refused). Sign in again.", … }}
    usr-omar is enabled: they can sign in.

    Le fournisseur a refusé le renouvellement, donc la session s’est terminée. Sans l’étape construite, cela arrive au plus 10 minutes après la désactivation du compte. La durée d’accès limite le maintien d’un accès périmé.

  4. Inactivité et limite absolue (construit). Connectez-vous comme Camille, placez sa dernière requête 31 minutes dans le passé, puis appelez :

    Fenêtre de terminal
    rm -f jar.txt
    signin usr-camille
    docker compose exec db psql -U tickets -c "UPDATE sessions SET last_seen_at = now() - interval '31 minutes' WHERE user_id = 'usr-camille' AND ended_at IS NULL;"
    me
    {"error":{"code":"session_ended","message":"Your session has ended (idle_timeout). Sign in again.", … }}

    Recommencez à partir de signin usr-camille, avec expires_at = now() - interval '1 second' plutôt que la modification de last_seen_at. Le message indique (absolute_timeout) (délai absolu).

  5. Déconnexion et copie de l’ancien témoin. Connectez Sam, gardez une copie du fichier de témoins, puis déconnectez-vous avec le jeton CSRF. Utilisez ensuite la copie :

    Fenêtre de terminal
    rm -f jar.txt
    signin usr-sam
    cp jar.txt sams-jar.txt
    CSRF=$(curl -s -b jar.txt http://127.0.0.1:8000/auth/session | python -c "import sys, json; print(json.load(sys.stdin)['csrf_token'])")
    curl -s -X POST -b jar.txt -c jar.txt -H "X-CSRF-Token: $CSRF" http://127.0.0.1:8000/auth/logout; echo
    curl -s -b sams-jar.txt http://127.0.0.1:8000/v1/me; echo
    {"signed_out":true,"provider_logout_url":"http://localhost:8400/logout?post_logout_redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2Fapp%2F"}
    {"error":{"code":"session_ended","message":"Your session has ended (logout). Sign in again.", … }}

    Supprimer le témoin dans le navigateur ne suffit pas, et l’API ne compte pas là-dessus. La ligne indique logout (déconnexion), donc un témoin copié est lui aussi inutilisable. L’API a aussi révoqué le jeton de renouvellement chez le fournisseur.

Comptez les fins dans votre base de données :

Fenêtre de terminal
docker compose exec db psql -U tickets -c "SELECT end_reason, count(*) FROM sessions GROUP BY 1 ORDER BY 1;"

L’exécution des mêmes étapes par l’auteur a donné un absolute_timeout, un idle_timeout, trois logout, un provider_refused et une session encore active (end_reason vide). Vos nombres dépendent du nombre d’essais.

Changement de compte : la connexion que personne n’a vue

Section intitulée « Changement de compte : la connexion que personne n’a vue »

L’étape 5 a maintenant une deuxième moitié. Sam s’est déconnecté de l’API. Ines s’assoit et exécute signin usr-ines avec le même fichier de témoins, comme la personne suivante dans le même navigateur :

Fenêtre de terminal
signin usr-ines
{"signed_in":true,"user_id":"usr-sam", … }

La réponse indique usr-sam. Le fournisseur a redirigé immédiatement, donc la fonction utilitaire n’a jamais atteint l’étape qui choisit usr-ines. C’est le piège de la bibliothèque, et le diagramme montre pourquoi :

Une séquence avec trois participants : le navigateur au bureau partagé, ticket-api et le fournisseur d'identité. Sam est connecté; l'API a défini ticket_session et le fournisseur a défini idp_session. Le navigateur envoie POST /auth/logout pour la session de Sam; l'API répond 200 et supprime ticket_session. Une note indique que idp_session est toujours là : le fournisseur connaît encore Sam. Ines s'assoit et clique sur Sign in. Le navigateur envoie GET /auth/login; l'API répond 302 vers /authorize. Le navigateur envoie GET /authorize avec idp_session. Le fournisseur répond directement 302 avec un code pour Sam, sans page, donc Ines ne voit jamais de choix. Le navigateur envoie GET /auth/callback avec le code, et l'API répond 302 /app/ avec une nouvelle session pour usr-sam. La page indique Signed in as usr-sam. Un rectangle vert montre la correction : Switch account ajoute prompt=select_account, et Sign out ouvre aussi l'URL de déconnexion du fournisseur. Avec prompt=select_account, le navigateur envoie GET /authorize et le fournisseur répond 200 avec la page de connexion, où Ines choisit Ines.
Deux sessions, deux témoins. Se déconnecter de l'API termine seulement sa session; la session du fournisseur reconnecte la personne précédente, sans rien afficher.
Description textuelle du diagramme

Suivez les deux témoins. ticket_session appartient à l’API, et la déconnexion l’a supprimé. idp_session appartient au fournisseur, sur un autre site, et l’API ne peut pas le supprimer. Quand la connexion suivante atteint le fournisseur, celui-ci voit sa propre session valide pour Sam. Il fait ce que permet l’authentification unique : il le connecte sans poser de question. Les vrais fournisseurs font la même chose.

Il y a deux corrections, et la page à /app/ utilise les deux.

  • Se déconnecter termine la session de l’API, puis envoie le navigateur vers provider_logout_url, fourni dans la réponse. C’est l’adresse de fin de session du fournisseur (déconnexion initiée par la partie utilisatrice, ou RP). Le fournisseur termine sa propre session et renvoie le navigateur à /app/.
  • Changer de compte termine la session de l’API et commence une nouvelle connexion avec prompt=select_account. Ce paramètre demande au fournisseur d’afficher sa page même s’il se souvient de quelqu’un. La session de Sam chez le fournisseur peut continuer; c’est son choix sur son appareil.

Essayez la deuxième correction avec la fonction utilitaire. Son deuxième argument ajoute du texte à l’adresse de connexion :

Fenêtre de terminal
CSRF=$(curl -s -b jar.txt http://127.0.0.1:8000/auth/session | python -c "import sys, json; print(json.load(sys.stdin)['csrf_token'])")
curl -s -X POST -b jar.txt -c jar.txt -H "X-CSRF-Token: $CSRF" http://127.0.0.1:8000/auth/logout > /dev/null
signin usr-ines "&prompt=select_account"
{"signed_in":true,"user_id":"usr-ines", … }

Le fournisseur a affiché sa page, et le choix lui est parvenu. Dans l’essai Chromium de l’auteur, Switch account (changer de compte) a fait la même chose. La page affichait Signed in as usr-ines (connecté comme usr-ines), sans aucune donnée de Sam. Après Sign out (se déconnecter), la déconnexion du fournisseur a renvoyé le navigateur à /app/, qui indiquait « Not signed in. » (non connecté). Le bouton Back (retour) affichait aussi « Not signed in. ».

Ce que le navigateur conserve de la personne précédente

Section intitulée « Ce que le navigateur conserve de la personne précédente »

Le changement de compte ne doit pas laisser les données de Sam sur l’écran d’Ines. Le projet l’empêche à trois endroits :

  • La page conserve les données uniquement en mémoire. clearData() dans app.html les efface à la déconnexion, avant toute autre action.
  • Chaque réponse authentifiée indique Cache-Control: no-store : /v1/me, /auth/session, /app/ et, à partir du module Contrôler l’accès aux ressources, chaque billet. Le navigateur et tout serveur mandataire ne conservent aucune copie. Le retour et l’historique ne peuvent donc pas montrer les réponses de Sam à Ines.
  • Chaque connexion reçoit une nouvelle session et un nouveau témoin. L’ancien témoin de Sam reste inutilisable (401 session_ended), même si quelqu’un l’a conservé.

Jusqu’ici, les fins après inactivité et à la limite absolue étaient construites. Rendez-en une réelle. Arrêtez fastapi dev avec Ctrl+C, puis redémarrez-le avec une limite d’inactivité d’une minute :

Fenêtre de terminal
SESSION_IDLE_MINUTES=1 fastapi dev

Connectez-vous avec signin usr-grace, appelez me, attendez 70 secondes, puis appelez me de nouveau. Prédisez les deux réponses avant d’exécuter les appels. Puis redémarrez l’API sans ce réglage (dans PowerShell, exécutez d’abord Remove-Item Env:SESSION_IDLE_MINUTES).

Afficher la réponse

Le premier me répond 200 avec les adhésions de Grace et repousse sa fin d’inactivité à une minute plus tard. Après 70 secondes sans requête, le deuxième me répond :

{"error":{"code":"session_ended","message":"Your session has ended (idle_timeout). Sign in again.", … }}

La durée d’accès (10 minutes) n’était pas écoulée. La vérification d’inactivité passe en premier, donc l’API n’a pas appelé le fournisseur.

Les trois nombres sont des choix, pas des lois de la nature. Choisissez-les avec les personnes qui utilisent la page.

  • Inactivité de 30 minutes convient à un service de soutien, où les gens s’absentent souvent. Une limite plus courte protège mieux les postes partagés. Mais les gens se connectent plus souvent et peuvent trouver un moyen de la contourner.
  • Durée absolue de 8 heures correspond à une journée de travail. Elle limite aussi l’utilisation d’un témoin volé : il fonctionne au plus une journée, pas pendant des semaines.
  • Durée d’accès de 10 minutes limite l’accès périmé accordé par le fournisseur : un compte désactivé perd son accès en 10 minutes. Une durée plus courte exige plus d’appels au fournisseur; chaque session active renouvelle son accès environ six fois par heure.

Les coûts : une écriture en base de données à chaque requête pour mesurer l’inactivité. Il faut aussi un appel au fournisseur toutes les 10 minutes par session active, et les gens doivent se reconnecter. Les limites du fournisseur comptent aussi : ici, son jeton de renouvellement dure 8 heures. L’API ne peut donc pas renouveler l’accès au-delà de cette durée.

La suppression d’une adhésion n’est pas une décision du fournisseur : Larkfield retire Camille dans sa propre table. À partir du module 4, l’API lit l’adhésion à chaque requête. Camille perd donc son accès à sa prochaine requête. Changements de cycle de vie mesure ce parcours.

Erreur courante

Tout le monde est déconnecté quelques minutes après chaque redémarrage

Symptôme : après un redémarrage de l’API, les gens restent connectés quelques minutes. C’est aussi le cas après un rechargement de fastapi dev déclenché par l’enregistrement d’un fichier. Ensuite, tout le monde reçoit 401 session_ended (provider_refused) (session terminée, refus du fournisseur), même si aucun compte n’a été désactivé. Au démarrage, le journal de l’API affichait SESSION_KEY is not set: sessions end when the API stops (SESSION_KEY n’est pas définie : les sessions se terminent quand l’API s’arrête).

Cause : SESSION_KEY manque dans .env. L’API crée donc une nouvelle clé aléatoire à chaque démarrage. Les jetons de renouvellement dans la table sessions ont été chiffrés avec l’ancienne clé. Les sessions continuent jusqu’à la fin de leur durée d’accès. Ensuite, l’API ne peut pas déchiffrer le jeton de renouvellement. Elle ne peut donc pas renouveler l’accès et termine la session.

Diagnostic : python -m scripts.check_identity, présenté dans la prochaine leçon, affiche PROBLEM SESSION_KEY is set (problème : SESSION_KEY est définie). Le message d’aide indique : « Sans cette clé, chaque redémarrage de l’API déconnecte tout le monde. » Dans la base de données, les sessions terminées ont end_reason = 'provider_refused' dans les 10 minutes suivant le redémarrage.

Correction :

  1. Vérifiez que .env contient une ligne SESSION_KEY=…. python -m idp init l’écrit; si vous avez recréé .env plus tard, elle manque.
  2. Exécutez python -m idp init --force pour écrire de nouveaux secrets. Cette commande crée aussi une nouvelle clé de signature et de nouveaux secrets client. Redémarrez ensuite le fournisseur et l’API.
  3. Connectez-vous de nouveau. Les sessions survivent maintenant à un redémarrage de l’API.

Sur un serveur, gardez SESSION_KEY dans le coffre de secrets, comme la clé d’API dans L’essentiel de Docker et du nuage pour les développeurs en IA. Si elle est divulguée, changez-la : chaque session se termine alors à son prochain renouvellement. C’est le résultat sûr.

Vérifiez votre compréhension

Pourquoi une session a-t-elle besoin d'un délai d'inactivité et d'une durée absolue? Donnez un cas que chacun détecte, mais pas l'autre.

Afficher la réponse

Le délai d’inactivité détecte une session oubliée. Sam quitte son poste à midi. La session se termine 30 minutes plus tard, même si les 8 heures ne sont pas écoulées. La durée absolue détecte une session qui reste toujours active. Une page qui envoie une requête chaque minute ne déclenche jamais le délai d’inactivité. C’est aussi le cas d’un témoin volé utilisé sans arrêt. La session se termine quand même 8 heures après la connexion. La fin absolue force aussi une nouvelle connexion. Le fournisseur vérifie donc de nouveau la personne et son deuxième facteur.

Vérifiez votre compréhension

Prédisez : Larkfield désactive Omar chez le fournisseur 2 minutes après son dernier renouvellement. Il continue de travailler dans son navigateur ouvert. Quand sa session se termine-t-elle, pour quelle raison, et pourquoi pas immédiatement?

Afficher la réponse

Environ 8 minutes plus tard, à sa première requête après la fin de la durée d’accès de 10 minutes. L’API tente alors de renouveler l’accès, mais le fournisseur refuse. La session se termine avec provider_refused (refus du fournisseur) : 401 session_ended (session terminée). Elle ne se termine pas immédiatement, car l’API n’interroge pas le fournisseur à chaque requête. La durée d’accès est la limite convenue pour l’accès périmé. Si 10 minutes est trop long, réduisez cette durée. Ou laissez le fournisseur prévenir l’API immédiatement. Il s’agit d’une déconnexion par canal arrière, non implémentée dans ce cours.

Vérifiez votre compréhension

Vérification finale : Sam est connecté à un poste partagé. Montrez, avec des commandes ou dans le navigateur, qu'Ines peut prendre sa place sans voir les données de Sam. Montrez aussi que l'ancienne session de Sam ne peut plus servir.

Afficher la réponse

Utilisez Switch account (changer de compte), ou déconnectez-vous, puis exécutez signin usr-ines "&prompt=select_account". Preuve : le fournisseur affiche sa page, et /auth/session répond "user_id":"usr-ines". La page affiche seulement le résultat de /v1/me pour Ines, car clearData() l’a vidée. Les réponses indiquent Cache-Control: no-store (ne pas stocker). Le bouton de retour n’affiche donc aucune donnée de Sam. Une copie du témoin de Sam reçoit 401 session_ended (logout) (session terminée, déconnexion). Montrez ensuite le problème que cela évite. Après une déconnexion de l’API seulement, sans prompt=select_account, la même commande signin usr-ines répond usr-sam.

Récapitulatif

  • Une session a trois compteurs : inactivité (30 minutes), durée absolue (8 h) et durée d’accès (10 minutes, renouvelée avec le jeton de renouvellement).
  • Elle peut se terminer de quatre façons, chacune enregistrée dans sessions.end_reason : logout (déconnexion), idle_timeout (délai d’inactivité), absolute_timeout (durée absolue), provider_refused. La prochaine requête reçoit 401 session_ended et perd le témoin.
  • Un compte désactivé chez le fournisseur perd sa session au prochain renouvellement, donc dans les 10 minutes. Un témoin copié ne fonctionne plus après la déconnexion.
  • Se déconnecter de l’API ne déconnecte pas du fournisseur. Sans précaution, le prochain clic sur « Sign in » (se connecter) reconnecte la personne précédente sans avertissement. Corrigez cela avec l’URL de déconnexion du fournisseur et avec prompt=select_account pour Switch account.
  • Aucune donnée de la personne précédente ne reste. La déconnexion vide la mémoire, chaque réponse personnelle contient Cache-Control: no-store, et chaque connexion reçoit un nouveau témoin.

La prochaine leçon examine l’autre côté de ces redirections. Elle présente ce que le fournisseur doit savoir sur l’API et ce qui change avec un vrai fournisseur.

Leçon suivante : Configuration du fournisseur