> Comprendre l'authentification JWT : la structure d'un JSON Web Token, le flux login-token-requêtes, signer et vérifier un jeton, le stockage cookie ou localStorage, le refresh token et la sécurité.

*Source : https://coder-studio.com/tutoriels/authentification-jwt/*

---

[// Backend](https://coder-studio.com/tutoriels/)

# L'authentification JWT expliquée (JSON Web Token)

Par **Kévin Papot** · publié le 16 février 2026

Dès qu’une application possède des comptes utilisateurs, une question se pose : comment le serveur sait-il, à chaque requête, qui est connecté ? Le **JWT** — pour _JSON Web Token_ — est aujourd’hui l’une des réponses les plus répandues, en particulier pour les API consommées par des applications web ou mobiles. Ce guide explique ce qu’est un JWT, comment se déroule le flux d’authentification, comment on signe et vérifie un jeton, et surtout quelles précautions de sécurité ne jamais négliger.

## La structure d’un JWT

Un **JSON Web Token** est une chaîne de caractères composée de trois parties séparées par des points : `header.payload.signature`. Chaque partie est un objet JSON encodé en Base64URL. Un jeton ressemble à ceci :

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsIm5vbSI6IkNhbWlsbGUifQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
```

Décomposons les trois segments :

-   Le **header** décrit le type de jeton et l’algorithme de signature utilisé, par exemple `{ "alg": "HS256", "typ": "JWT" }`.
-   Le **payload** contient les _claims_, c’est-à-dire les informations sur l’utilisateur : son identifiant (`sub`), éventuellement son rôle, et des champs standard comme `exp` (date d’expiration) ou `iat` (date d’émission). Par exemple `{ "sub": "42", "nom": "Camille", "exp": 1739700000 }`.
-   La **signature** est calculée à partir du header, du payload et d’une clé secrète connue du seul serveur. C’est elle qui garantit que le jeton n’a pas été altéré.

Point crucial pour un débutant : le payload est **encodé, pas chiffré**. N’importe qui peut le décoder et lire son contenu. On ne place donc jamais d’information sensible (mot de passe, numéro de carte) dans un JWT. Ce que la signature garantit, ce n’est pas la confidentialité, c’est l’**intégrité** : impossible de modifier le payload sans invalider la signature, faute de connaître le secret.

## Le flux d’authentification

Le principe du JWT repose sur un modèle **sans état** (_stateless_) : le serveur ne conserve pas de session en mémoire. Le déroulé typique compte trois temps.

D’abord, l’utilisateur **se connecte** en envoyant ses identifiants (email et mot de passe) à une route de login. Le serveur les vérifie, et s’ils sont valides, il **génère un JWT** signé avec sa clé secrète, puis le renvoie au client.

Ensuite, à **chaque requête** vers une route protégée, le client renvoie ce jeton, généralement dans l’en-tête HTTP `Authorization` :

```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5c...
```

Enfin, le serveur **vérifie la signature** du jeton reçu. Si elle est valide et le jeton non expiré, il en déduit l’identité de l’utilisateur — sans jamais consulter de base de données de sessions. C’est cette absence d’état qui fait la force du JWT pour les API distribuées : n’importe quel serveur connaissant le secret peut valider le jeton. Ce mécanisme s’intègre naturellement dans une [API REST](https://coder-studio.com/tutoriels/api-rest-guide/), où chaque requête est indépendante.

## Signer un jeton à la connexion

En pratique, on n’implémente pas la cryptographie à la main : on utilise une bibliothèque éprouvée comme `jsonwebtoken` dans l’écosystème Node.js. Voici une route de login avec Express :

```
import express from "express";
import jwt from "jsonwebtoken";

const app = express();
app.use(express.json());

const SECRET = process.env.JWT_SECRET; // jamais en dur dans le code

app.post("/login", async (req, res) => {
  const { email, motDePasse } = req.body;

  const utilisateur = await trouverUtilisateur(email);
  if (!utilisateur || !(await verifierMotDePasse(motDePasse, utilisateur.hash))) {
    return res.status(401).json({ erreur: "Identifiants invalides" });
  }

  const token = jwt.sign(
    { sub: utilisateur.id, nom: utilisateur.nom },
    SECRET,
    { expiresIn: "15m" }
  );

  res.json({ token });
});
```

Trois éléments méritent d’être soulignés. Le mot de passe est comparé à un **hash** stocké en base — jamais au mot de passe en clair, car on ne stocke jamais de mots de passe en clair. Le secret provient d’une variable d’environnement, pas du code source. Et l’option `expiresIn: "15m"` fixe une durée de vie courte au jeton, un choix de sécurité que nous justifierons plus bas.

## Vérifier un jeton sur les routes protégées

Côté serveur, la vérification se factorise dans un **middleware** appliqué à toutes les routes qui exigent une authentification :

```
function authentifier(req, res, next) {
  const entete = req.headers.authorization;
  if (!entete || !entete.startsWith("Bearer ")) {
    return res.status(401).json({ erreur: "Jeton manquant" });
  }

  const token = entete.slice(7); // retire "Bearer "
  try {
    const donnees = jwt.verify(token, SECRET);
    req.utilisateur = donnees; // { sub, nom, iat, exp }
    next();
  } catch (e) {
    return res.status(401).json({ erreur: "Jeton invalide ou expiré" });
  }
}

// route protégée
app.get("/profil", authentifier, (req, res) => {
  res.json({ id: req.utilisateur.sub, nom: req.utilisateur.nom });
});
```

La fonction `jwt.verify` fait tout le travail : elle recalcule la signature et la compare, vérifie que le jeton n’est pas expiré, et lève une exception dans le cas contraire. Si tout est valide, on attache les informations de l’utilisateur à `req.utilisateur`, accessibles ensuite dans la route.

## Où stocker le jeton côté client ?

C’est l’une des questions les plus débattues, car elle engage la sécurité. Deux options principales s’offrent au client web.

Le **localStorage** est simple : le jeton s’y range et se lit en JavaScript pour l’ajouter à l’en-tête `Authorization`. Son défaut majeur est la vulnérabilité aux attaques **XSS** (injection de script) : un script malveillant injecté dans la page peut lire le localStorage et voler le jeton.

Le **cookie** — idéalement avec les attributs `HttpOnly`, `Secure` et `SameSite` — est plus sûr contre le XSS, car un cookie `HttpOnly` est inaccessible au JavaScript. En contrepartie, il expose potentiellement aux attaques **CSRF**, qu’on contre avec l’attribut `SameSite` et des jetons anti-CSRF.

```
res.cookie("token", token, {
  httpOnly: true,   // inaccessible au JavaScript du navigateur
  secure: true,     // transmis uniquement en HTTPS
  sameSite: "strict",
  maxAge: 15 * 60 * 1000 // 15 minutes
});
```

Il n’y a pas de réponse universelle, mais pour une application web classique, le cookie `HttpOnly` + `Secure` + `SameSite` offre le meilleur compromis. Le localStorage se justifie surtout quand le client n’est pas un navigateur (application mobile, service à service).

## Le refresh token

Nous avons donné au jeton une durée de vie de 15 minutes. C’est volontairement court : si un jeton est volé, la fenêtre d’exploitation reste limitée. Mais reconnecter l’utilisateur toutes les 15 minutes serait pénible. La solution est le couple **access token / refresh token**.

L’**access token** est ce JWT à courte durée de vie, envoyé à chaque requête. Le **refresh token**, lui, a une durée de vie longue (plusieurs jours) et sert uniquement à obtenir un nouvel access token quand celui-ci expire, sans redemander le mot de passe. On le stocke de façon plus protégée (cookie `HttpOnly`) et, contrairement à l’access token, on le conserve côté serveur pour pouvoir le **révoquer** — par exemple lors d’une déconnexion ou d’un vol suspecté.

```
app.post("/refresh", async (req, res) => {
  const { refreshToken } = req.cookies;
  const valide = await refreshTokenExisteEnBase(refreshToken);
  if (!valide) return res.status(401).json({ erreur: "Refresh invalide" });

  const donnees = jwt.verify(refreshToken, SECRET_REFRESH);
  const nouveauToken = jwt.sign({ sub: donnees.sub }, SECRET, { expiresIn: "15m" });
  res.json({ token: nouveauToken });
});
```

Ce mécanisme concilie confort d’usage et sécurité : jetons de session courts et jetables, jeton de renouvellement long mais révocable.

## Les règles de sécurité à ne jamais oublier

Le JWT est puissant mais impardonnable en cas de négligence. Quelques principes non négociables :

-   **Garder le secret secret.** La clé de signature ne doit jamais apparaître dans le code source ni dans un dépôt Git. Elle vit dans une variable d’environnement et doit être longue et aléatoire.
-   **Toujours fixer une expiration.** Un jeton sans `exp` reste valide éternellement. Une durée courte pour l’access token est la meilleure défense contre le vol.
-   **Servir en HTTPS uniquement.** Sans chiffrement du transport, un jeton peut être intercepté sur le réseau.
-   **Ne rien mettre de sensible dans le payload.** Il est lisible par quiconque possède le jeton.
-   **Prévoir la révocation.** Comme un JWT est sans état, on ne peut pas l’invalider unilatéralement avant son expiration ; d’où l’intérêt des refresh tokens stockés et révocables côté serveur.

## En résumé

Le JWT est un jeton signé en trois parties — header, payload, signature — qui permet une authentification sans état : le serveur émet le jeton à la connexion, le client le renvoie à chaque requête, et le serveur en vérifie simplement la signature. Sa simplicité et son adéquation aux API en ont fait un standard, mais il exige de la rigueur : secret bien gardé, expiration courte, HTTPS, stockage réfléchi et refresh tokens pour la révocation. Bien maîtrisé, il sécurise élégamment n’importe quelle API moderne.

## À lire ensuite

-   [Créer une API REST avec Express.js](https://coder-studio.com/tutoriels/express-js-api/) — le cadre dans lequel s’intègrent les middlewares d’authentification.
-   [API REST : principes et bonnes pratiques](https://coder-studio.com/tutoriels/api-rest-guide/) — pour situer l’authentification dans une architecture REST.
-   [Le CRUD expliqué : Create, Read, Update, Delete](https://coder-studio.com/tutoriels/crud-guide/) — les opérations que vos routes protégées vont exposer.
