Authentification JWT : Comment Fonctionnent les JSON Web Tokens

· 12 min de lecture

Table des Matières

Qu'est-ce qu'un JSON Web Token ?

Un JSON Web Token (JWT) est un moyen compact et sécurisé pour les URL de représenter des revendications entre deux parties. Les JWT sont devenus la norme de facto pour l'authentification et l'autorisation dans les applications web modernes, les API et les architectures de microservices.

Lorsqu'un utilisateur se connecte, le serveur génère un JWT contenant les informations de l'utilisateur et l'envoie au client. Le client inclut ensuite ce token dans les requêtes suivantes pour prouver son identité. Pensez-y comme un passeport numérique qui contient vos identifiants et peut être vérifié sans rappeler l'autorité émettrice à chaque fois.

Contrairement à l'authentification traditionnelle basée sur les sessions où le serveur maintient l'état de session en mémoire ou dans une base de données, les JWT sont sans état. Toutes les informations nécessaires pour vérifier le token sont contenues dans le token lui-même. Cette décision architecturale apporte plusieurs avantages :

Vous pouvez inspecter et décoder n'importe quel JWT en utilisant notre outil de décodage JWT pour voir son contenu sans avoir besoin d'écrire de code. Ceci est particulièrement utile lors du débogage de problèmes d'authentification ou pour comprendre quelles données vos tokens contiennent.

Conseil pro : Les JWT sont signés, pas chiffrés par défaut. N'importe qui peut décoder et lire le contenu d'un JWT. Ne stockez jamais d'informations sensibles comme des mots de passe, des numéros de carte de crédit ou des numéros de sécurité sociale dans les charges utiles JWT.

Structure JWT Expliquée

Un JWT se compose de trois parties distinctes séparées par des points (.) : l'En-tête, la Charge Utile et la Signature. Voici à quoi ressemble un vrai JWT :

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZW1haWwiOiJqb2huQGV4YW1wbGUuY29tIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDI2MjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Décomposons chaque composant pour comprendre comment ils fonctionnent ensemble pour créer un token sécurisé et vérifiable.

1. En-tête

L'en-tête contient généralement deux champs critiques qui définissent comment le token doit être traité :

{
  "alg": "HS256",
  "typ": "JWT"
}

Cet objet JSON est ensuite encodé en Base64Url pour former la première partie du JWT. L'encodage le rend sûr pour les URL et compact pour la transmission dans les en-têtes HTTP.

2. Charge Utile

La charge utile contient des revendications — des déclarations sur l'utilisateur et des métadonnées supplémentaires. C'est ici que vous stockez les données réelles que vous souhaitez transmettre :

{
  "sub": "1234567890",
  "name": "John Doe",
  "email": "[email protected]",
  "role": "admin",
  "iat": 1516239022,
  "exp": 1516242622
}

Les revendications sont divisées en trois catégories :

Revendications enregistrées (standardisées et recommandées) :

Revendications publiques sont des revendications personnalisées qui doivent être définies dans le registre IANA JSON Web Token ou utiliser des noms résistants aux collisions (comme des URL).

Revendications privées sont des revendications personnalisées convenues entre les parties, comme role, permissions ou department.

3. Signature

La signature est ce qui rend les JWT sécurisés et inviolables. Elle est créée en prenant l'en-tête encodé, la charge utile encodée, une clé secrète et en appliquant l'algorithme spécifié dans l'en-tête :

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

La signature remplit deux objectifs critiques :

  1. Vérification de l'intégrité : Garantit que le token n'a pas été modifié depuis son émission
  2. Authentification : Prouve que le token a été créé par quelqu'un ayant accès à la clé secrète

Si quelqu'un tente de modifier l'en-tête ou la charge utile, la vérification de la signature échouera et le token sera rejeté.

Comment Fonctionne l'Authentification JWT

Comprendre le flux d'authentification complet est essentiel pour implémenter correctement les JWT. Voici le processus étape par étape :

Étape 1 : Connexion de l'Utilisateur

L'utilisateur soumet ses identifiants (nom d'utilisateur et mot de passe) au point de terminaison d'authentification. Le serveur valide ces identifiants par rapport à la base de données.

Étape 2 : Génération du Token

Si les identifiants sont valides, le serveur génère un JWT contenant les informations de l'utilisateur et les revendications. Le serveur signe le token en utilisant une clé secrète (pour les algorithmes symétriques) ou une clé privée (pour les algorithmes asymétriques).

Étape 3 : Livraison du Token

Le serveur renvoie le JWT au client, généralement dans le corps de la réponse. Le client stocke ce token pour les requêtes futures.

Étape 4 : Requêtes Authentifiées

Pour les requêtes suivantes, le client inclut le JWT dans l'en-tête Authorization en utilisant le schéma Bearer :

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Étape 5 : Vérification du Token

Le serveur reçoit la requête, extrait le JWT de l'en-tête et vérifie la signature en utilisant la clé secrète ou publique. Si valide, le serveur extrait les informations de l'utilisateur de la charge utile et traite la requête.

Étape 6 : Expiration du Token

Lorsque le token expire, le client doit obtenir un nouveau token, soit en se réauthentifiant, soit en utilisant un token de rafraîchissement (couvert dans la section suivante).

Conseil rapide : Validez toujours la revendication exp côté serveur, même si vous vérifiez l'expiration côté client. Les vérifications côté client peuvent être contournées, mais la validation côté serveur fait autorité.

Tokens d'Accès vs Tokens de Rafraîchissement

Un système d'authentification JWT robuste utilise généralement deux types de tokens fonctionnant ensemble : les tokens d'accès et les tokens de rafraîchissement. Comprendre la distinction est crucial pour construire des applications sécurisées.

Tokens d'Accès

Les tokens d'accès sont des JWT de courte durée (généralement 15 minutes à 1 heure) qui accordent l'accès aux ressources protégées. Ils contiennent l'identité de l'utilisateur et les permissions nécessaires pour autoriser les requêtes.

Caractéristiques :

Tokens de Rafraîchissement

Les tokens de rafraîchissement sont des tokens de longue durée (jours à mois) utilisés exclusivement pour obtenir de nouveaux tokens d'accès. Ils sont stockés en toute sécurité et envoyés uniquement au point de terminaison de rafraîchissement du token.

Caractéristiques :

Pourquoi Utiliser les Deux ?

Cette approche à double token équilibre sécurité et expérience utilisateur :

Aspect Token d'Accès Uniquement Accès + Tokens de Rafraîchissement
Sécurité Inférieure (tokens longue durée à risque) Supérieure (tokens d'accès courte durée)
Expérience Utilisateur Meilleure (pas de réauthentification) Meilleure (rafraîchissement transparent du token)
Révocation Difficile (sans état) Possible (tokens de rafraîchissement en BD)
Surcharge Réseau Inférieure Légèrement supérieure (requêtes de rafraîchissement)
Complexité d'Implémentation Simple Modérée

Flux de Rafraîchissement du Token

  1. Le client détecte que le token d'accès est expiré ou sur le point d'expirer
  2. Le client envoie le token de rafraîchissement au point de terminaison /auth/refresh
  3. Le serveur valide le token de rafraîchissement par rapport à la base de données
  4. Le serveur génère un nouveau token d'accès (et éventuellement un nouveau token de rafraîchissement)
  5. Le client reçoit et stocke les nouveaux tokens
  6. Le client réessaie la requête originale avec le nouveau token d'accès

Conseil pro : Implémentez la rotation des tokens de rafraîchissement — émettez un nouveau token de rafraîchissement à chaque utilisation et invalidez l'ancien. Cela limite les dégâts si un token de rafraîchissement est compromis.

Guide d'Implémentation Node.js

Construisons un système d'authentification JWT complet en utilisant Node.js, Express et la bibliothèque jsonwebtoken. Cette implémentation inclut à la fois les tokens d'accès et de rafraîchissement.

Installation

npm install express jsonwebtoken bcrypt dotenv

Configuration de l'Environnement

Créez un fichier .env avec vos clés secrètes :

ACCESS_TOKEN_SECRET=votre-cle-secrete-acces-super-secrete-changez-ceci
REFRESH_TOKEN_SECRET=votre-cle-secrete-rafraichissement-super-secrete-changez-ceci
ACCESS_TOKEN_EXPIRY=15m
REFRESH_TOKEN_EXPIRY=7d

Génération de Token

const jwt = require('jsonwebtoken');

function generateAccessToken(user) {
  return jwt.sign(
    { 
      userId: user.id,
      email: user.email,
      role: user.role 
    },
    process.env.ACCESS_TOKEN_SECRET,
    { expiresIn: process.env.ACCESS_TOKEN_EXPIRY }
  );
}

function generateRefreshToken(user) {
  return jwt.sign(
    { userId: user.id },
    process.env.REFRESH_TOKEN_SECRET,
    { expiresIn: process.env.REFRESH_TOKEN_EXPIRY }
  );
}

Point de Terminaison de Connexion

const bcrypt = require('bcrypt');

app.post('/auth/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    
    // Trouver l'utilisateur dans la base de données
    const user = await User.findOne({ email });
    if (!user) {
      return res.status(401).json({ error: 'Identifiants invalides' });
    }
    
    // Vérifier le mot de passe
    const validPassword = await bcrypt.compare(password, user.password);
    if (!validPassword) {
      return res.status(401).json({ error: 'Identifiants invalides' });
    }
    
    // Générer les tokens
    const accessToken = generateAccessToken(user);
    const refreshToken = generateRefreshToken(user);
    
    // Stocker le token de rafraîchissement dans la base de données
    await RefreshToken.create({
      token: refreshToken,
      userId: user.id,
      expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
    });
    
    res.json({
      accessToken,
      refreshToken,
      user: {
        id: user.id,
        email: user.email,
        name: user.name
      }
    });
  } catch (error) {
    res.status(500).json({ error: 'Erreur serveur' });
  }
});

Middleware d'Authentification

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'Token d\'accès requis' });
  }
  
  jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
    if (err) {
      return res.status(403).json({ error: 'Token invalide ou expiré' });
    }
    
    req.user = user;
    next();
  });
}

// Utilisation
app.get('/api/protected', authenticateToken, (req, res) => {
  res.json({ 
    message: 'Données protégées',
    user: req.user 
  });
});

Point de Terminaison de Rafraîchissement du Token

app.post('/auth/refresh', async (req, res) => {
  const { refreshToken } = req.body;
  
  if (!refreshToken) {
    return res.status(401).json({ error: 'Token de rafraîchissement requis' });
  }
  
  try {
    // Vérifier que le token de rafraîchissement existe dans la base de données
    const storedToken = await RefreshToken.findOne({ token: refreshToken });
    if (!storedToken) {
      return res.status(403).json({ error: 'Token de rafraîchissement invalide' });
    }
    
    // Vérifier la signature du token
    jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, async (err, user) => {
      if (err) {
        return res.status(403).json({ error: 'Token de rafraîchissement invalide' });
      }
      
      // Obtenir les données de l'utilisateur
      const userData = await User.findById(user.userId);
      
      // Générer un nouveau token d'accès
      const accessToken = generateAccessToken(userData);
      
      res.json({ accessToken });