Déconnectez un utilisateur d’une application basée sur les sessions, et le serveur l’oublie dès la requête suivante. Déconnectez-le d’une application basée sur JWT et, à moins d’avoir mis en place un mécanisme supplémentaire, le token qu’il détient reste valide jusqu’à son expiration. Cette différence est la vraie décision derrière « JWT contre session ». Tout le reste en découle.
Ce qu’une session stocke réellement
Une session garde l’état de l’utilisateur sur le serveur et ne transmet au navigateur qu’une clé de référence.
- L’utilisateur se connecte avec un mot de passe.
- Le serveur crée un enregistrement de session (ID utilisateur, rôles, tout ce qu’il lui faut) et le stocke en mémoire, dans une base de données ou sur Redis, indexé par un ID de session aléatoire.
- Le serveur envoie cet ID au navigateur sous forme de cookie, marqué
HttpOnlypour que le JavaScript côté client ne puisse pas le lire. - À chaque requête, le navigateur renvoie automatiquement le cookie. Le serveur recherche l’ID dans le store et récupère les données de session.
Le cookie ne transporte aucune information sur l’utilisateur, seulement une chaîne opaque que le serveur résout à chaque requête. Supprimez l’enregistrement dans le store et la session disparaît dès la requête suivante, sur tous les appareils.
Ce qu’il y a à l’intérieur d’un JWT
Un JSON Web Token évite cette recherche en plaçant l’état directement dans le token, signé pour qu’il ne puisse pas être altéré.
Un JWT se compose de trois parties encodées en base64url et séparées par des points : header.payload.signature. Le payload est un objet JSON de claims. La RFC 7519 définit une courte liste de claims enregistrés :
| Claim | Nom | Signification |
|---|---|---|
iss | Issuer | Qui a émis le token |
sub | Subject | De qui parle le token |
aud | Audience | À qui le token est destiné |
exp | Expiration | Rejeter le token après ce moment |
nbf | Not before | Rejeter le token avant ce moment |
iat | Issued at | Quand le token a été créé |
jti | JWT ID | ID unique, utile pour la révocation et la détection de replay |
Le serveur signe le header et le payload avec un secret (HMAC) ou une clé privée (RSA/ECDSA) et envoie le résultat au client. N’importe quel serveur possédant la clé correspondante (ou, pour une signature asymétrique, seulement la clé publique) peut vérifier la signature et faire confiance aux claims. Aucun aller-retour vers la base de données, aucun store de sessions partagé. Dans un système à plusieurs services, chacun peut vérifier un token de son côté.
Le revers de la médaille : quiconque détient le token peut lire le payload. Le base64 est un encodage, pas un chiffrement. Ne mettez jamais un mot de passe, un secret ou des données personnelles que vous n’imprimeriez pas dans un log dans le payload d’un JWT.
Comment configurer l’authentification par session dans Express
Une configuration Node classique utilise express-session comme middleware et connect-redis pour que les sessions survivent à un redémarrage du serveur et fonctionnent sur plusieurs instances. Si Redis est nouveau pour vous, comment fonctionne le cache Redis couvre les bases pour le faire tourner.
| |
| |
req.session.destroy() supprime l’entrée dans Redis. La requête suivante avec ce cookie ne trouve rien et est traitée comme une déconnexion. Pas de liste noire de tokens, rien à retoucher plus tard.
Comment configurer l’authentification JWT dans Node
Le même flux avec jsonwebtoken, en émettant un access token de courte durée et un refresh token séparé :
| |
| |
jwt.verify vérifie la signature et les claims exp/nbf en un seul appel, et lève une exception si l’un des deux échoue. C’est tout le contrôle d’authentification, sans aucun store impliqué.
L’option algorithms: ["HS256"] compte. Sans elle, un token signé avec un algorithme différent de celui prévu pourrait quand même passer, ce qui explique comment plusieurs bibliothèques JWT se sont retrouvées avec des vulnérabilités d’« alg confusion ». Fixez en dur la liste d’algorithmes acceptés à la vérification et rejetez systématiquement alg: "none".
Le refresh token est ce qui rend vivable un access token de 15 minutes. Le client le conserve et, quand l’access token expire, l’échange contre une nouvelle paire. Stockez le jti du refresh token côté serveur pour pouvoir révoquer cette seule chaîne sans toucher aux access tokens déjà en circulation.
Pourquoi les JWT sont difficiles à révoquer
Une session est un enregistrement dans un store : supprimez-le et l’utilisateur est déconnecté partout. Un JWT est un claim autonome que n’importe quel vérificateur accepte jusqu’à ce que exp dise le contraire. Se déconnecter, bannir un utilisateur ou rétrograder son rôle ne change rien à un token déjà émis, car le serveur qui vérifie n’a aucun enregistrement à supprimer.
Les mitigations habituelles :
- Durée de vie courte de l’access token. 15 à 30 minutes est la plage la plus courante, pour qu’un token volé ou périmé n’ait qu’une petite fenêtre.
- Rotation du refresh token. Chaque refresh émet un nouveau refresh token et invalide l’ancien ; réutiliser un ancien refresh token révoque toute la chaîne. OAuth 2.1 l’exige (ou des tokens sender-constrained) pour les clients publics.
- Une liste noire ciblée. Pour tuer un token précis avant son expiration, indexez la liste noire sur
jtiplusiss, pas sur la chaîne brute du token. Le OWASP JWT Cheat Sheet explique pourquoi mettre la chaîne elle-même en liste noire n’est pas fiable. Une liste noire vérifiée à chaque requête remet aussi une recherche en base de données à chaque requête, exactement le coût que les JWT étaient censés supprimer.
Si votre application finit par faire une recherche à chaque vérification de toute façon, une session aurait été le choix le plus simple.
Où stocker un JWT dans le navigateur
Dans une application navigateur, placez le token dans un cookie HttpOnly et Secure, pas dans localStorage. N’importe quel script exécuté sur la page peut lire localStorage, donc une seule faille XSS livre tous les tokens qu’il contient. Le coût : une fois que le token voyage dans un cookie, le navigateur l’envoie automatiquement, et vous voilà de retour à vous défendre contre le CSRF avec SameSite et des tokens CSRF, exactement comme avec les sessions. La comparaison complète se trouve dans LocalStorage, SessionStorage ou Cookies : que choisir ?.
Quand utiliser les sessions et quand utiliser les JWT
| Situation | Utilisez |
|---|---|
| Une seule application rendue côté serveur, un seul backend | Sessions : plus simples, révocation immédiate |
| Déconnexion instantanée ou changements de permissions instantanés | Sessions, ou JWT avec une liste noire sur jti |
| Plusieurs services backend qui vérifient l’utilisateur chacun de son côté | JWT : aucun store de sessions partagé à solliciter |
| Application mobile ou clients API tiers | JWT : les bearer tokens correspondent à une API HTTP sans état |
Aucun des deux n’est plus sûr dans l’absolu. Une session sur Redis et un JWT de 15 minutes avec des refresh tokens rotés tiennent tout aussi bien. La différence est architecturale : combien de services doivent faire confiance au claim, et à quelle vitesse vous devez pouvoir le retirer.
Vous construisez une application avec un seul backend ? Choisissez les sessions par défaut et obtenez la révocation gratuitement. Passez aux JWT le jour où un second service doit savoir qui est l’utilisateur sans rappeler le service qui l’a authentifié.