Accueil / Articles / Sessions contre JWT : choisir le bon modèle d’authentification pour Node.js

Sessions contre JWT : choisir le bon modèle d’authentification pour Node.js

Découvrez en quoi les sessions et les JWT diffèrent réellement dans l’authentification Node.js, comment chacun d’eux fonctionne, et comment choisir entre eux sans le regretter par la suite.

1335 mots

Le problème commun aux deux approches

HTTP ne dispose d’aucune mémoire intégrée pour conserver des informations entre deux requêtes. Chaque requête entrante semble provenir d’un inconnu, à moins qu’elle ne contienne une preuve d’identité. Les sessions et les JWT résolvent ce problème de la même manière fondamentale : ils fournissent au client un élément de données à renvoyer lors de chaque requête ultérieure. Ce qui les distingue réellement, c’est la nature de cet élément de données et, plus important encore, l’endroit où se trouve le registre officiel indiquant « qui est connecté ».

Option un : Les sessions

Dans une configuration basée sur les sessions, c’est le serveur qui détient l’autorité. Lorsqu’un utilisateur se connecte, le serveur crée un identifiant de session aléatoire, enregistre les informations réelles de l’utilisateur associées à cet identifiant dans un système comme Redis ou une base de données, et ne transmet au client que cet identifiant lui-même, généralement stocké dans un cookie.

app.post("/login", async (req, res) => {
  const user = await authenticate(req.body.email, req.body.password);
  const sessionId = generateSecureId();

  await redis.set(`session:${sessionId}`, JSON.stringify({ userId: user.id }), "EX", 86400);
  res.cookie("sessionId", sessionId, { httpOnly: true, secure: true });
  res.json({ success: true });
});

app.use(async (req, res, next) => {
  const sessionId = req.cookies.sessionId;
  const session = await redis.get(`session:${sessionId}`);
  req.user = session ? JSON.parse(session).userId : null;
  next();
});

Dès lors, chaque demande entrante déclenche une recherche de l’endroit où se trouvent les données de session. Ce mécanisme unique explique à la fois pourquoi les sessions sont utiles et pourquoi elles entraînent des coûts supplémentaires.

Les avantages : vous avez un contrôle immédiat et total sur ceux qui restent connectés. Mettre fin à une session, que ce soit suite au déconnexion d’un utilisateur, à un réinitialisation de mot de passe ou à la fermeture par un administrateur d’un compte compromis, se résume simplement à une suppression dans le stockage des sessions. Il n’est pas nécessaire d’attendre que quelque chose expire naturellement.

Les inconvénients : chaque demande nécessite désormais un aller-retour vers le stockage des sessions, ce qui ajoute une légère mais réelle latence ainsi qu’une charge supplémentaire sur l’infrastructure. De plus, cela transforme votre stockage des sessions en un état partagé auquel tous vos serveurs doivent avoir accès, ce qui devient un véritable problème d’architecture dès que vous dépassez le cadre d’un seul serveur.

Option deux : JWT

app.post("/login", async (req, res) => {
  const user = await authenticate(req.body.email, req.body.password);
  const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: "1h" });
  res.cookie("token", token, { httpOnly: true, secure: true });
  res.json({ success: true });
});


app.use((req, res, next) => {
  try {
    const decoded = jwt.verify(req.cookies.token, process.env.JWT_SECRET);
    req.user = decoded.userId;
  } catch {
    req.user = null;
  }
  next();
});

Aucune requête vers une base de données ni vers Redis n’est nécessaire. La signature cryptographique seule suffit à confirmer que le jeton n’a pas été altéré depuis sa délivrance.

Avantages : il n’y a pas de recherche dans la base de données ou dans le cache à chaque requête, ce qui rend les opérations nettement plus rapides, et il n’existe pas de stockage de sessions partagé que tous les serveurs devraient consulter, ce qui simplifie l’échelle horizontale et rend les architectures de microservices sans état beaucoup plus faciles à comprendre.

L’équilibre à trouver : une fois qu’un JWT a été délivré, le serveur ne dispose d’aucun moyen natif pour le rendre invalide avant son expiration. Si le jeton est volé, ou si l’accès d’un utilisateur doit être interrompu immédiatement, le serveur ne peut rien supprimer, car rien de central n’a jamais été stocké à l’origine. C’est cet équilibre qui prend souvent les gens par surprise, généralement après qu’ils ont déjà conçu leur système en partant du principe que le JWT n’est qu’une version plus rapide et améliorée d’une session.

La idée fausse qui provoque de vrais problèmes

L’expression « les JWT sont sans état » est utilisée à tort comme argument de vente si fréquemment que les gens négligent ce qu’elle implique réellement : être sans état signifie également, par défaut, être sans possibilité de révocation. Si un cookie de session est compromis, il suffit de le supprimer pour résoudre le problème. En revanche, un JWT volé reste pleinement valide et entièrement fiable pour votre serveur tant que dure sa période de validité, à moins que vous n’ayez pris des mesures supplémentaires pour y remédier.

La solution habituelle consiste à maintenir une liste noire des tokens révoqués :

app.use(async (req, res, next) => {
  try {
    const decoded = jwt.verify(req.cookies.token, process.env.JWT_SECRET);
    const isRevoked = await redis.get(`revoked:${decoded.jti}`);
    if (isRevoked) throw new Error("Token revoked");
    req.user = decoded.userId;
  } catch {
    req.user = null;
  }
  next();
});

Cela fonctionne bien, mais observez de près ce que vous venez de faire : vous avez réintroduit une vérification par requête contre un stockage de données partagé, ce qui est précisément le surcoût que les JWT étaient censés éliminer. À ce stade, vous n’avez plus vraiment un système sans état. Ce que vous avez, c’est un système basé sur des sessions déguisé, avec un mode de défaillance encore plus complexe en dessous.

Alors lequel devriez-vous vraiment utiliser ?

Préférez les sessions lorsque : vous avez besoin d’une révocation immédiate et fiable (pensez aux applications bancaires, aux tableaux de bord administratifs ou à tout système où les enjeux de sécurité sont élevés), que vous exécutez une application derrière un chargeur de travail capable d’accéder facilement à un stockage de sessions partagé, ou que vous préférez honnêtement gérer une seule source de vérité plutôt que de jongler avec les durées de vie des tokens et la logique de liste noire.

Préférez les JWT lorsque : vous gérez l’authentification entre des services véritablement indépendants qui n’ont pas tous besoin d’un accès direct à un stockage de sessions partagé, vous développez un système où le fait de rendre les tokens à durée de vie courte (minutes plutôt que jours) rend l’intervalle entre révocation et utilisation acceptable, ou la raison réelle pour laquelle vous souhaitez un étatless est que vous servez de nombreux utilisateurs d’API indépendants, et non simplement parce que cela semble plus propre.

Une nuance à souligner : en pratique, la plupart des systèmes de production ne choisissent pas clairement un côté. Le modèle retenu est celui de JWT de courte durée combinés à un jeton de renouvellement conservé du côté serveur. Il s’agit d’un mélange plutôt que d’une alternative binaire : les sessions gèrent la confiance à long terme ainsi que leur révocation, tandis que les JWT couvrent des périodes de vérification courtes et sans état entre deux actions. Si vous avez tendance à considérer le choix entre « sessions » et « JWT » comme une décision binaire, c’est généralement un signe que vous n’avez pas encore atteint une échelle où ce système hybride justifie sa complexité, ce qui signifie qu’une simple session constitue probablement un point de départ plus honnête et simplifié.

La décision réelle

Au fond, la question n’a jamais été purement technique : il s’agit en réalité de décider où les coûts seront supportés. Les sessions imputent ces coûts à chaque demande, mais en retour vous obtenez un contrôle toujours à jour, jamais obsolète. Les JWT éliminent ce coût par demande, mais le prix à payer est une fenêtre de temps pendant laquelle ce que votre serveur croit et ce qui est réellement vrai peuvent silencieusement s’éloigner l’un de l’autre. Aucune de ces approches ne mérite l’étiquette de « moderne » ou d’« obsolète », quel que soit le cadre dans lequel on les aborde. Ce ne sont simplement que deux endroits différents où placer le même compromis qui disparaît jamais vraiment.

Lectures complémentaires

  • Choisir entre EC2, ECS et EKS pour des travaux Node.js — Compare la manière dont EC2, ECS avec Fargate et EKS gèrent opérationnellement les applications Node.js, vous aidant à sélectionner le service de calcul AWS adapté à l’échelle et aux compétences de votre équipe.
  • Mettre en œuvre des jetons d’accès et de réapprovisionnement ensemble dans Node.js — Apprenez comment combiner des jetons d’accès à courte durée de vie avec des jetons de réapprovisionnement rotatifs dans Node.js afin d’équilibrer la sécurité et des sessions utilisateur fluides.
  • Construire une authentification JWT prête pour la production dans des API Node.js — Comment hacher les mots de passe, émettre des JWT à durée de vie courte, ajouter des tokens de renouvellement et un mécanisme de révocation, séparer l’autorisation, résister aux attaques par force brute et s’assurer que l’authentification échoue correctement.
  • Quand l’authentification JWT devient étatique : un cas pour des séances du côté du serveur dans Node — Découvrez comment les listes de blocage de révocation et les stockages de tokens de renouvellement rendent l’authentification JWT fragile, comment les séances Express basées sur Postgres la simplifient, et dans quels cas les JWT restent pertinents.