Gestion des sessions pour applications web : cookies, JWT et tokens de rafraîchissement
Comparaison des stratégies de gestion de session pour applications web : sessions par cookie, JWT et tokens de rafraîchissement, illustrées par des modèles de menace concrets et des exigences réalistes de déconnexion.

Ce que fait réellement la gestion des sessions
Une session, c'est la façon dont votre application répond à une question après la connexion : "Qui êtes-vous en ce moment ?" Une fois que cette réponse est fiable, l'app peut décider ce que l'utilisateur peut voir, modifier, ou quelles actions doivent être bloquées.
« Rester connecté » est aussi un choix de sécurité. Vous décidez pendant combien de temps une identité utilisateur reste valide, où se trouve la preuve d'identité, et ce qui se passe si cette preuve est copiée.
La plupart des configurations d'app web s'appuient sur trois éléments de base :
- Sessions côté serveur basées sur les cookies : le navigateur stocke un cookie, et le serveur recherche la session à chaque requête.
- JWT (tokens d'accès) : le client envoie un token signé que le serveur peut vérifier sans consulter une base de données.
- Refresh tokens (tokens de rafraîchissement) : un identifiant durable utilisé pour obtenir de nouveaux tokens d'accès de courte durée.
Ce ne sont pas tant des « styles » concurrents que des façons différentes de gérer les mêmes compromis : rapidité vs contrôle, simplicité vs flexibilité, et « peut-on révoquer ça immédiatement ? » vs « ça expire tout seul ? »
Une façon utile d'évaluer une conception : si un attaquant vole la preuve que votre appli utilise (cookie ou token), que peut-il faire, et pendant combien de temps ? Les sessions par cookie sont souvent gagnantes quand vous avez besoin d'un contrôle fort côté serveur, comme une déconnexion forcée ou un verrouillage instantané. Les JWT conviennent quand vous voulez des vérifications sans état entre services, mais ils deviennent problématiques quand il faut une révocation immédiate.
Aucune option ne l'emporte partout. Le bon choix dépend de votre modèle de menace, de la sévérité des exigences de déconnexion et de la complexité que votre équipe peut raisonnablement maintenir.
Modèles de menace qui changent la bonne réponse
Un bon design de session dépend moins du « meilleur » type de token que des attaques que vous devez réellement survivre.
Si un attaquant vole des données du stockage du navigateur (comme localStorage), les tokens d'accès JWT sont faciles à récupérer puisque le JavaScript de la page peut les lire. Un cookie volé est différent : s'il est configuré en HttpOnly, le code de la page ne peut pas le lire, donc les attaques simples de type « vol de token » sont plus difficiles. Mais si l'attaquant a l'appareil (ordinateur perdu, malware, ordinateur partagé), les cookies peuvent toujours être copiés depuis le profil du navigateur.
XSS (du code d'attaquant exécuté sur votre page) change tout. Avec XSS, l'attaquant peut ne pas avoir besoin de voler quoi que ce soit : il peut utiliser la session déjà connectée de la victime pour effectuer des actions. Les cookies HttpOnly empêchent la lecture des secrets de session, mais ils n'empêchent pas un attaquant d'envoyer des requêtes depuis la page.
CSRF (un site différent déclenchant des actions non désirées) menace surtout les sessions basées sur les cookies, car les navigateurs attachent automatiquement les cookies. Si vous comptez sur les cookies, vous devez avoir des défenses CSRF claires : réglages SameSite intentionnels, tokens anti-CSRF, et gestion prudente des requêtes qui modifient l'état. Les JWT envoyés dans un en-tête Authorization sont moins exposés au CSRF classique, mais restent vulnérables au XSS si vous les stockez là où le JavaScript peut les lire.
Les attaques par rejeu (réutiliser une information d'auth volée) sont là où les sessions côté serveur brillent : vous pouvez invalider un ID de session immédiatement. Des JWT de courte durée réduisent la fenêtre de rejeu, mais n'empêchent pas la réutilisation tant que le token est valide.
Les appareils partagés et les téléphones perdus font de la « déconnexion » un vrai modèle de menace. Les décisions se résument souvent à des questions : un utilisateur peut-il forcer la déconnexion sur d'autres appareils, à quelle vitesse cela doit-il prendre effet, que se passe-t-il si un refresh token est volé, autorisez-vous des sessions « se souvenir de moi » ? Beaucoup d'équipes appliquent des standards plus stricts au personnel qu'aux clients, ce qui modifie les durées et les attentes de révocation.
Sessions par cookie : comment ça marche et ce que ça protège
Les sessions basées sur les cookies sont la configuration classique. Après l'authentification, le serveur crée un enregistrement de session (souvent un ID plus des champs comme l'ID utilisateur, la date de création et la durée). Le navigateur ne stocke que l'ID de session dans un cookie. À chaque requête, le navigateur renvoie ce cookie et le serveur regarde la session pour décider qui est l'utilisateur.
Le grand avantage en sécurité est le contrôle. La session est validée côté serveur à chaque fois. Si vous devez expulser quelqu'un, vous supprimez ou désactivez l'enregistrement de session côté serveur et il cesse de fonctionner immédiatement, même si l'utilisateur a encore le cookie.
Beaucoup de protection vient des paramètres du cookie :
- HttpOnly : empêche JavaScript de lire le cookie.
- Secure : n'envoie le cookie qu'en HTTPS.
- SameSite : limite quand le navigateur envoie le cookie sur des requêtes cross-site.
L'endroit où vous stockez l'état de session affecte la montée en charge. Garder les sessions en mémoire d'application est simple, mais pose problème quand vous avez plusieurs serveurs ou des redémarrages fréquents. Une base de données assure la durabilité. Redis est courant quand on veut des recherches rapides et beaucoup de sessions actives. L'idée clé : le serveur doit pouvoir retrouver et valider la session à chaque requête.
Les sessions par cookie conviennent bien quand vous avez besoin d'un comportement de déconnexion strict, comme des tableaux de bord du personnel ou des portails clients où un administrateur doit pouvoir forcer la déconnexion après un changement de rôle. Si un employé part, désactiver ses sessions côté serveur coupe l'accès immédiatement, sans attendre l'expiration des tokens.
Tokens d'accès JWT : forces et limites
Un JWT (JSON Web Token) est une chaîne signée qui contient quelques revendications sur l'utilisateur (comme l'ID, le rôle, le tenant) plus une date d'expiration. Votre API vérifie la signature et l'expiration localement, sans appeler une base de données, puis autorise la requête.
C'est pourquoi les JWT sont populaires dans les produits API-first, les applications mobiles et les systèmes où plusieurs services doivent valider la même identité. Si vous avez plusieurs instances backend, chacune peut vérifier le token et obtenir la même réponse.
Points forts
Les tokens d'accès JWT sont rapides à vérifier et faciles à transmettre avec les appels API. Si votre frontend appelle de nombreux endpoints, un token d'accès de courte durée peut garder le flux simple : vérifier la signature, lire l'ID utilisateur, continuer.
Exemple : un portail client appelle « Liste des factures » et « Mettre à jour le profil » sur des services séparés. Un JWT peut porter l'ID client et un rôle comme customer, de sorte que chaque service peut autoriser la requête sans recherche de session à chaque fois.
Limites
Le plus grand compromis est la révocation. Si un token est valide pendant une heure, il est généralement valable partout pendant cette heure, même si l'utilisateur clique sur « se déconnecter » ou si un admin désactive le compte, sauf si vous ajoutez des vérifications côté serveur.
Les JWT fuient aussi de façons banales. Les points de défaillance courants incluent localStorage (XSS peut le lire), la mémoire du navigateur (extensions malveillantes), les logs et rapports d'erreur, les proxies et outils d'analytics qui capturent les en-têtes, et les tokens copiés dans des chats de support ou des captures d'écran.
À cause de cela, les tokens d'accès JWT conviennent mieux pour un accès de courte durée, pas pour une connexion « pour toujours ». Gardez-les minimaux (pas de données personnelles sensibles dedans), une expiration courte, et supposez qu'un token volé sera utilisable jusqu'à son expiration.
Refresh tokens : rendre les setups JWT viables
Les tokens d'accès JWT sont conçus pour être de courte durée. C'est bon pour la sécurité, mais ça crée un problème pratique : les utilisateurs ne devraient pas avoir à se reconnecter toutes les quelques minutes. Les refresh tokens résolvent cela en permettant à l'application d'obtenir silencieusement un nouveau token d'accès quand l'ancien expire.
L'endroit où vous stockez le refresh token compte encore plus que pour le token d'accès. Dans une application web côté navigateur, la valeur la plus sûre par défaut est un cookie HttpOnly et Secure pour que JavaScript ne puisse pas le lire. Le stockage local est plus facile à implémenter, mais il est aussi plus facile à voler si vous avez un bug XSS. Si votre modèle de menace inclut le XSS, évitez de placer des secrets de longue durée dans un stockage accessible au JavaScript.
La rotation est ce qui rend les refresh tokens praticables en production. Plutôt que d'utiliser le même refresh token pendant des semaines, vous l'échangez à chaque utilisation : le client présente le refresh token A, le serveur émet un nouveau token d'accès plus le refresh token B, et A devient invalide.
Un réglage de rotation simple suit en général quelques règles :
- Garder les tokens d'accès courts (minutes, pas heures).
- Stocker les refresh tokens côté serveur avec un statut et la date de dernière utilisation.
- Faire une rotation à chaque rafraîchissement et invalider le token précédent.
- Lier les refresh tokens à un appareil ou navigateur quand c'est possible.
- Journaliser les événements de refresh pour pouvoir enquêter en cas d'abus.
La détection de réutilisation est l'alarme clé. Si le refresh token A a déjà été échangé, mais que vous le voyez à nouveau plus tard, supposez qu'il a été copié. Une réponse courante est de révoquer toute la session (et souvent toutes les sessions de cet utilisateur) et d'exiger une nouvelle connexion, car vous ne pouvez pas savoir quelle copie est la bonne.
Pour la déconnexion, il faut quelque chose que le serveur puisse faire respecter. Cela implique généralement une table de sessions (ou une liste de révocation) qui marque les refresh tokens comme révoqués. Les tokens d'accès peuvent encore fonctionner jusqu'à leur expiration, mais vous pouvez garder cette fenêtre courte en utilisant des tokens d'accès de courte durée.
Exigences de déconnexion et ce qui est réellement applicable
La déconnexion semble simple jusqu'à ce que vous la définissiez. Il y a généralement deux demandes différentes : « déconnecter cet appareil » (un navigateur ou un téléphone) et « déconnecter partout » (toutes les sessions actives sur tous les appareils).
Il y a aussi une question de délai. « Déconnexion immédiate » signifie que l'app n'accepte plus la preuve maintenant. « Déconnexion après expiration » signifie que l'app n'accepte plus la preuve lorsque la session ou le token expire naturellement.
Avec les sessions par cookie, la déconnexion immédiate est simple parce que le serveur contrôle la session. Vous supprimez le cookie côté client et invalidez l'enregistrement de session côté serveur. Si quelqu'un a copié la valeur du cookie plus tôt, c'est le rejet côté serveur qui fait réellement appliquer la déconnexion.
Avec une authentification uniquement par JWT (tokens d'accès sans état et sans vérification serveur), vous ne pouvez pas garantir véritablement une déconnexion immédiate. Un JWT volé reste valide jusqu'à son expiration, car le serveur n'a nulle part où vérifier « ce token est-il révoqué ? ». Vous pouvez ajouter une denylist, mais alors vous conservez de l'état et le vérifiez, ce qui enlève une grande partie de la simplicité d'origine.
Un schéma pratique est de traiter les tokens d'accès comme de courte durée et d'appliquer la déconnexion via les refresh tokens. Le token d'accès peut continuer à fonctionner pendant quelques minutes, mais c'est le refresh token qui maintient la session en vie. Si un ordinateur portable est volé, révoquer la famille de refresh tokens coupe rapidement l'accès futur.
Ce que vous pouvez promettre raisonnablement aux utilisateurs :
- Déconnecter cet appareil : révoquer la session ou le refresh token correspondant, et supprimer les cookies ou le stockage local.
- Déconnecter partout : révoquer toutes les sessions ou toutes les familles de refresh tokens pour le compte.
- Effet « immédiat » : garanti avec des sessions serveur, meilleur effort avec des tokens d'accès jusqu'à leur expiration.
- Événements de déconnexion forcée : changement de mot de passe, compte désactivé, rétrogradation de rôle.
Pour les changements de mot de passe et la désactivation de compte, ne comptez pas sur « l'utilisateur va se déconnecter ». Stockez une version de session côté compte (ou un timestamp « token valide après »). À chaque rafraîchissement (et parfois à chaque requête), comparez-le. S'il a changé, refusez et demandez une nouvelle connexion.
Étape par étape : choisir une approche de session pour votre appli
Si vous voulez que la conception des sessions reste simple, décidez d'abord de vos règles puis choisissez la mécanique. La plupart des problèmes commencent quand les équipes choisissent JWT ou cookies parce que c'est populaire, pas parce que ça correspond aux risques et aux exigences de déconnexion.
Commencez par lister tous les endroits où un utilisateur se connecte. Une application web se comporte différemment d'une application mobile native, d'un outil admin interne ou d'une intégration partenaire. Chacun change ce qui peut être stocké en toute sécurité, comment les connexions sont renouvelées, et ce que « déconnexion » doit signifier.
Un ordre pratique qui fonctionne pour la plupart des équipes :
- Listez vos clients : web, iOS/Android, outils internes, accès tiers.
- Choisissez un modèle de menace par défaut : XSS, CSRF, appareil volé.
- Décidez ce que la déconnexion doit garantir : cet appareil, tous les appareils, déconnexion forcée par admin.
- Choisissez un schéma de base : sessions côté serveur (le serveur se rappelle) ou tokens d'accès + refresh tokens.
- Définissez les durées et règles de réponse : expiration inactive vs absolue, et ce que vous faites en cas de réutilisation suspecte.
Puis documentez les promesses exactes de votre système. Ex : « Les sessions web expirent après 30 minutes d'inactivité ou 7 jours en tout. Un admin peut forcer la déconnexion en moins de 60 secondes. Un téléphone perdu peut être désactivé à distance. » Ces phrases importent plus que la librairie que vous utilisez.
Enfin, ajoutez du monitoring qui correspond à votre schéma. Pour les setups à base de tokens, un signal fort est la réutilisation d'un refresh token (le même refresh token utilisé deux fois). Traitez cela comme un vol probable, révoquez la famille de sessions et alertez l'utilisateur.
Erreurs courantes qui mènent à des prises de contrôle de compte
La plupart des prises de contrôle de compte ne sont pas des « hacks sophistiqués ». Ce sont des victoires simples dues à des erreurs prévisibles dans la gestion des sessions. Bien gérer les sessions, c'est surtout ne pas offrir aux attaquants un moyen facile de voler ou rejouer des identifiants.
Une erreur courante est de placer des tokens d'accès dans localStorage en espérant ne jamais avoir de XSS. Si n'importe quel script s'exécute sur votre page (une dépendance mal sécurisée, un widget injecté, un commentaire stocké), il peut lire localStorage et envoyer le token ailleurs. Les cookies avec le flag HttpOnly réduisent ce risque parce que JavaScript ne peut pas les lire.
Un autre piège est de rendre les JWT de longue durée pour éviter les refresh tokens. Un token d'accès de 7 jours est une fenêtre de réutilisation de 7 jours s'il fuit. Un token d'accès court + un refresh token bien géré est plus difficile à abuser, surtout si vous pouvez couper la possibilité de rafraîchir.
Les cookies apportent leur propre risque : oublier les défenses CSRF. Si votre appli utilise des cookies pour authentifier et accepte des requêtes qui modifient l'état sans protection CSRF, un site malveillant peut pousser le navigateur connecté à envoyer des requêtes valides.
Autres erreurs souvent identifiées après incident :
- Les refresh tokens ne sont jamais rotatifs, ou ils tournent mais la réutilisation n'est pas détectée.
- Vous supportez plusieurs méthodes de connexion (session cookie et bearer token) mais la règle serveur « laquelle prime » est floue.
- Les tokens se retrouvent dans des logs (console navigateur, événements d'analytics, logs de requêtes serveur), où ils sont copiés et conservés.
Un exemple concret : un agent de support colle un "log de debug" dans un ticket. Le log inclut un en-tête Authorization. Quiconque a accès au ticket peut rejouer ce token et agir en tant qu'agent. Traitez les tokens comme des mots de passe : ne les imprimez pas, ne les stockez pas inutilement, et gardez-les de courte durée.
Vérifications rapides avant de mettre en production
La plupart des bugs de session ne concernent pas de la crypto sophistiquée. Ils viennent d'un flag manquant, d'un token qui vit trop longtemps, ou d'un endpoint qui aurait dû demander une ré-authentification.
Avant la mise en production, faites un passage rapide focalisé sur ce qu'un attaquant peut faire avec un cookie ou token volé. C'est l'un des moyens les plus rapides d'améliorer la sécurité sans réécrire toute l'authentification.
Checklist pré-release
Passez ces contrôles en staging, puis à nouveau en production :
- Gardez les tokens d'accès courts (minutes), et vérifiez que l'API les rejette bien après expiration.
- Traitez les refresh tokens comme des mots de passe : stockez-les là où le JavaScript ne peut pas les lire si possible, envoyez-les uniquement à l'endpoint de refresh, et faites une rotation à chaque usage.
- Si vous utilisez des cookies pour l'auth, vérifiez les flags : HttpOnly activé, Secure activé, et SameSite configuré intentionnellement. Confirmez aussi que le scope du cookie (domaine et chemin) n'est pas plus large que nécessaire.
- Si les cookies authentifient les requêtes, ajoutez des défenses CSRF, et confirmez que les endpoints modifiant l'état échouent sans le signal CSRF.
- Rendez la révocation réelle : après un reset de mot de passe ou une désactivation de compte, les sessions existantes doivent cesser de fonctionner rapidement (suppression côté serveur, invalidation des refresh tokens, ou vérification d'une "version de session").
Après cela, testez vos promesses de déconnexion. « Se déconnecter » signifie souvent « supprimer la session locale », mais les utilisateurs en attendent plus.
Un test pratique : connectez-vous sur un laptop et un téléphone, puis changez le mot de passe. Le laptop doit être expulsé à la prochaine requête, pas des heures plus tard. Si vous offrez « déconnecter partout » et une liste d'appareils, confirmez que chaque appareil correspond à un enregistrement de session ou de refresh token distinct que vous pouvez révoquer.
Exemple : un portail client avec comptes du personnel et déconnexion forcée
Imaginez une petite entreprise avec un portail client web (clients consultent factures, ouvrent des tickets) et une application mobile pour le personnel sur le terrain (tâches, notes, photos). Le personnel travaille parfois hors réseau, donc l'app doit fonctionner un peu hors ligne. Les admins veulent aussi un gros bouton rouge : si une tablette est perdue ou un contractuel part, ils peuvent forcer une déconnexion.
Ajoutez trois menaces courantes : tablettes partagées dans des camionnettes (quelqu'un oublie de se déconnecter), phishing (un employé saisit ses identifiants sur une fausse page), et un bug XSS occasionnel dans le portail (un script s'exécute dans le navigateur et tente de voler ce qu'il peut).
Une configuration pratique ici est des tokens d'accès de courte durée plus des refresh tokens rotatifs, avec une révocation côté serveur. Cela vous donne des appels API rapides et une tolérance hors ligne, tout en permettant aux admins de couper les sessions.
Voici ce que cela peut donner :
- Durée du token d'accès : 5 à 15 minutes.
- Rotation des refresh tokens : à chaque refresh, on renvoie un nouveau refresh token et l'ancien est invalidé.
- Stockage sécurisé des refresh tokens : sur le web, garder le refresh token dans un cookie HttpOnly et Secure ; sur mobile, le stocker dans le stockage sécurisé du système.
- Suivi côté serveur des refresh tokens : stocker un enregistrement du token (utilisateur, appareil, date d'émission, dernière utilisation, flag révoqué). Si un token rotatif est réutilisé, traitez-le comme un vol et révoquez toute la chaîne.
La déconnexion forcée devient applicable : l'admin révoque l'enregistrement du refresh token pour cet appareil (ou tous les appareils de l'utilisateur). L'appareil volé peut continuer d'utiliser le token d'accès courant jusqu'à son expiration, mais il ne pourra pas obtenir un nouveau token. Ainsi, le temps maximal pour couper l'accès totalement est la durée de vie du token d'accès.
Pour un appareil perdu, définissez la règle en clair : « Dans les 10 minutes, l'app cessera de synchroniser et demandera une nouvelle connexion. » Le travail hors ligne peut rester sur l'appareil, mais la prochaine synchronisation en ligne doit échouer tant que l'utilisateur n'est pas reconnecté.
Prochaines étapes : implémenter, tester et garder la solution maintenable
Rédigez en langage produit ce que « déconnexion » signifie. Par exemple : « Se déconnecter supprime l'accès sur cet appareil », « Se déconnecter partout expulse tous les appareils sous 1 minute », ou « Changer le mot de passe déconnecte les autres sessions ». Ces promesses déterminent si vous avez besoin d'état côté serveur, de listes de révocation ou de tokens courts.
Transformez ces promesses en un petit plan de tests. Les bugs de tokens et sessions sont souvent invisibles en démonstration en conditions idéales, puis apparaissent en conditions réelles (veille, réseaux instables, appareils multiples).
Checklist de tests pratiques
Exécutez des tests couvrant les cas désordonnés :
- Expiration : l'accès s'arrête quand le token d'accès ou la session expire, même si le navigateur reste ouvert.
- Révocation : après « déconnecter partout », la vieille preuve échoue à la prochaine requête.
- Rotation : la rotation des refresh tokens émet un nouveau refresh token et invalide l'ancien.
- Détection de réutilisation : rejouer un ancien refresh token déclenche un verrouillage.
- Multi-appareils : les règles « cet appareil seulement » vs « tous les appareils » sont appliquées et l'interface utilisateur reflète cela.
Après les tests, faites un exercice d'attaque simple avec votre équipe. Choisissez trois scénarios et parcourez-les de bout en bout : un bug XSS qui peut lire des tokens, une tentative CSRF contre des sessions par cookie, et un téléphone volé avec une session active. Vous vérifiez que la conception correspond à vos promesses.
Si vous devez aller vite, réduisez le code personnalisé. AppMaster (appmaster.io) est une option quand vous voulez un backend généré prêt pour la production plus des apps web et natives, afin de garder des règles comme l'expiration, la rotation et la déconnexion forcée cohérentes entre les clients.
Planifiez une revue après le lancement. Utilisez les tickets de support réels et les incidents pour ajuster les durées, les limites de sessions et le comportement de « déconnecter partout », puis relancez la checklist pour éviter les régressions silencieuses.


