Aller au contenu principal

JWT

Qu'est-ce qu'un JWT ?

Un JSON Web Token (JWT) est un standard de sécurité pour transmettre des informations sur le Web (HTTP) entre un client Web (comme un navigateur) et un serveur Web.

Les JWT sont très utilisés pour l'authentification dans les applications Web modernes, car ils permettent de sécuriser les échanges sans nécessiter de session serveur.

Les JWT sont définis par la spécification RFC 7519.

Structure d'un JWT

Un JWT est une chaîne de caractères qui contient trois parties, séparées par des points (.) :


  1. Header (En-tête)

L'en-tête contient des informations sur le type de token (JWT) et l'algorithme de signature (par ex. HS256).


  1. Payload (Charge utile)

La charge utile contient les données que l'on souhaite transmettre.

Il existe 3 types de données que l'on peut inclure dans le Payload d'un JWT :

  • Données enregistrées : Ce sont des éléments standardisés, comme :

    • iss (issuer, l'émetteur du token),

    • sub (subject, le sujet du token),

    • exp (expiration, la date d'expiration au format timestamp Unix),

    • etc.

    Ces clés (appelées claims) ont des noms et des usages prédéfinis.

    Comment convertir une date au format timestamp Unix ?

    Pour convertir une date au format timestamp Unix, on peut utiliser l'une des commandes suivantes :

    • sous Linux
      date -d "14/11/2024" +%s
    • sous Windows
      $date = Get-Date "14/11/2024"
      $timestamp = [int]($date - (Get-Date "1970-01-01")).TotalSeconds
      Write-Output $timestamp
    • sous MacOS
      date -j -f "%d/%m/%Y" "14/11/2024" +%s

  • Données publiques : Ce sont des éléments que l'on peut définir librement, tant qu'ils ne sont pas en conflit avec les données enregistrées.

    Par exemple, on pourrait ajouter un champ name ou role pour spécifier le nom ou le rôle de l'utilisateur.

  • Données privées : Ce sont des éléments personnalisés, utilisés uniquement entre certaines parties et non standardisés.

    Par exemple, un champ department pour indiquer le département de l'utilisateur dans une entreprise.


  1. Signature

    La signature assure l'intégrité du token. Elle est générée en combinant le Header, le Payloadet une clé :

    • une clé secrète (avec le chiffrement symétrique HS256)
    • ou une clé privée (avec le chiffrement asymétrique RS256).

  • Exemple d'une clé secrète avec le chiffrement symétrique HS256 : 72ee2095f2774b069cab0419e4947207e48e56ec95e4a065cd4682cac9fb2f06

    Comment générer une clé secrète ?

    Pour générer une clé secrète avec le chiffrement symétrique HS256, on peut utiliser l'une des commandes suivantes :

    • sous Linux
      openssl rand -hex 32
    • sous Windows
      $bytes = New-Object byte[] 32
      [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
      $hexSecret = [BitConverter]::ToString($bytes) -replace '-', ''
      Write-Output $hexSecret
    • sous MacOS
      openssl rand -hex 32

    astuce

    Consultez le tutoriel sur le Chiffrement pour plus d'infos sur la différence Chiffrement symétrique / Chiffrement asymétrique.


Exemple d'un JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJMb2dpY2llbCBkZSBnZXN0aW9uIGRlcyBzdGFnZXMiLCJzdWIiOiJVdGlsaXNhdGV1ciBjb25uZWN0w6kiLCJleHAiOjE3MzE2MTc2NjEsImVtYWlsIjoicGllcnJlLnJvZkBqb2xpY2llbC5vcmcifQ.PP2aNjItC9byFPA_n_i_Ix6tktlbo7IbDfTUt8H8tlY

Fonctionnement des JWT

Fonctionnement des JWT pour l'authentification

  1. Authentification initiale : Lorsqu'un utilisateur se connecte, le serveur génère un JWT et le renvoie au client.

  2. Stockage et envoi : Le client stocke le JWT (dans un cookie sécurisé ou le localStorage) et l'envoie dans l'en-tête Authorization pour chaque requête suivante.

  3. Vérification : Le serveur vérifie la signature du JWT pour s'assurer qu'il est valide et n'a pas été modifié. Si le token est valide et non expiré, le serveur accorde l'accès.

Si la clé secrète (ou privée) est connue par un autre serveur, la vérification de la signature du JWT peut être réalisée par celui ci.

Ainsi, la connexion peut être réalisée sur le 1er serveur, puis les requêtes suivantes peuvent être effectuées sur le 2ème serveur.

Avantages des JWT

  • Sans état : Le serveur n'a pas besoin de stocker des sessions (le JWT contient toutes les informations).
  • Sécurisé : Les tokens sont signés, empêchant leur modification.
  • Portable : Les JWT peuvent être utilisés dans différents contextes (API, microservices).

Précautions de cybersécurité

  • Expiration : Assurez-vous de définir une date d'expiration (exp) pour éviter les tokens périmés.
  • Sécurité des tokens : Les JWT doivent être stockés de manière sécurisée (ex : dans un cookie HttpOnly).
  • Révocation : Contrairement aux sessions, révoquer un JWT est plus complexe. On peut mettre en place une "liste noire" pour les tokens invalidés.

Usages des JWT

Est-ce qu'un JWT peut être utilisé pour autre chose que l'authentification ?

Bien qu'il soit souvent associé à l'authentification dans les applications Web, un JWT est en fait un moyen générique de transmettre des informations de manière sécurisée entre deux parties.

Les JWT sont donc très polyvalents et peuvent être utilisés dans tous les scénarios où l'on souhaite transmettre des informations de manière sécurisée et sans état. Bien que leur usage le plus courant soit l'authentification et l'autorisation, ils peuvent servir dans des applications variées, notamment dans des architectures distribuées et sans session.

Attention toutefois : bien que les JWT soient pratiques, il est crucial de gérer correctement leur sécurité, notamment en définissant une expiration adéquate et en stockant les tokens de manière sécurisée côté client, pour éviter les risques d'interception et de vol de token.

astuce

Pour plus d'infos, vous pouvez consulter le site dédié aux JSON Web Tokens : https://jwt.io/


Test de mémorisation/compréhension


Quelle partie du JWT contient l'algorithme de signature ?


Quel claim est utilisé pour l'expiration d'un JWT ?


Comment le JWT est-il transmis à chaque requête ?


TP - Création d'un JWT avec Node.js

Objectif

  1. Créer un JWT côté serveur.
  2. Simuler une authentification en envoyant le JWT au client.
  3. Valider le JWT pour contrôler l'accès aux ressources protégées.

Création du serveur

  1. Initialisez un projet Node.js :

    npm init -y
    npm install jsonwebtoken express
  2. Créez un fichier server.js et configurez Express pour générer un JWT lors de l'authentification :

    const express = require('express');
    const jwt = require('jsonwebtoken');
    const app = express();
    const SECRET_KEY = 'votre_clé_secrète';

    app.use(express.json());

    app.post('/login', (req, res) => {
    const { username } = req.body;
    if (username) {
    const token = jwt.sign({ username }, SECRET_KEY, { expiresIn: '1h' });
    res.json({ token });
    } else {
    res.status(400).send('Nom d\'utilisateur requis');
    }
    });
  3. Créez une route protégée qui vérifie le JWT :

    app.get('/protected', (req, res) => {
    const token = req.headers['authorization']?.split(' ')[1];
    if (!token) return res.sendStatus(401);

    jwt.verify(token, SECRET_KEY, (err, user) => {
    if (err) return res.sendStatus(403);
    res.json({ message: 'Accès autorisé à la ressource protégée', user });
    });
    });

    app.listen(3000, () => console.log('Serveur démarré sur le port 3000'));

Tester avec le client

Lancez le serveur et utilisez un outil comme Postman pour tester les routes /login et /protected :

  1. POST /login : Cette route génère un JWT après qu’un utilisateur fournit un nom d’utilisateur. Le serveur renvoie le token en réponse.
  2. GET /protected : Cette route est protégée par une vérification du JWT. L’utilisateur doit envoyer le JWT dans les en-têtes pour y accéder. Si le JWT est valide, le serveur accorde l'accès ; sinon, il renvoie un code d’erreur.

Prérequis

  • Assurez-vous que votre serveur Node.js est démarré et écoute sur le port 3000 (ou le port que vous avez configuré).
  • Postman est installé et prêt à l’emploi.

Étape 1 : Tester la route /login pour obtenir un JWT

  1. Ouvrez Postman et cliquez sur New > Request pour créer une nouvelle requête.

  2. Nommez la requête (par exemple, "Login") et choisissez un dossier où la sauvegarder si nécessaire.

  3. Dans la requête :

    • Méthode HTTP : Sélectionnez POST.
    • URL : Entrez http://localhost:3000/login.
  4. Configurer le corps de la requête :

    • Sélectionnez l'onglet Body.
    • Choisissez l'option raw et assurez-vous que le type est JSON (vous pouvez le sélectionner dans le menu déroulant à droite de "Text").
    • Dans le corps de la requête, entrez un JSON avec un nom d’utilisateur. Par exemple :
      {
      "username": "testUser"
      }
  5. Envoyer la requête :

    • Cliquez sur Send.
    • Le serveur devrait répondre avec un objet JSON contenant un token, comme ceci :
      {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
      }
    • Copiez la valeur du token (le long texte entre guillemets). Ce token sera utilisé pour accéder à la route protégée /protected.

Étape 2 : Tester la route /protected en envoyant le JWT

  1. Créer une nouvelle requête dans Postman pour accéder à la route /protected.

    • Cliquez sur New > Request pour créer une nouvelle requête.
    • Nommez la requête (par exemple, "Access Protected Route") et choisissez un dossier où la sauvegarder si nécessaire.
  2. Configurer la requête :

    • Méthode HTTP : Sélectionnez GET.
    • URL : Entrez http://localhost:3000/protected.
  3. Ajouter le JWT dans les en-têtes :

    • Allez dans l’onglet Headers.

    • Cliquez sur + pour ajouter un nouvel en-tête.

    • Dans la première case (clé), entrez Authorization.

    • Dans la deuxième case (valeur), entrez Bearer suivi du token que vous avez copié précédemment. Cela devrait ressembler à :

      Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

    Note : Assurez-vous qu'il y a un espace entre Bearer et le token.

    Que signifie en anglais, le terme "bearer" ?

    En anglais, le terme "bearer" signifie "porteur" ou "détenteur".

    Dans le contexte des "Bearer Tokens" (tokens porteurs) dans l’authentification, "bearer" signifie que celui qui possède le token (le porteur du token) est considéré comme authentifié et autorisé à accéder à la ressource protégée. En d’autres termes, il suffit de présenter le token pour prouver son identité et obtenir l’accès.

    Quand on utilise l'en-tête Authorization: Bearer <token>, cela signifie donc que l'on envoie un jeton d'accès (ou token JWT) qui prouve qu'on est autorisé à accéder à la ressource. Le serveur vérifie simplement la validité du token pour accorder ou refuser l’accès, sans demander de mot de passe ou de session supplémentaire.

    Exemple :

    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

    Dans cet exemple, Bearer indique que le jeton qui suit est un "bearer token". Tant que le client présente ce token valide, il est considéré comme authentifié.

  4. Envoyer la requête :

    • Cliquez sur Send.
    • Si le token est valide, le serveur devrait répondre avec un message confirmant l’accès. Par exemple :
      {
      "message": "Accès autorisé à la ressource protégée",
      "user": {
      "username": "testUser",
      "iat": 1637764774,
      "exp": 1637768374
      }
      }
    • Si le token est invalide ou absent, vous recevrez une réponse avec un code d'erreur, comme 401 Unauthorized (si le token est manquant) ou 403 Forbidden (si le token est invalide ou expiré).

Résumé des étapes dans Postman

  1. Route /login :

    • Méthode : POST
    • URL : http://localhost:3000/login
    • Body : JSON avec { "username": "testUser" }
    • Résultat attendu : Un token JWT est renvoyé.
  2. Route /protected :

    • Méthode : GET
    • URL : http://localhost:3000/protected
    • Header Authorization : Bearer <votre_token>
    • Résultat attendu : Accès autorisé si le token est valide, sinon un message d'erreur.
Automatisation dans Postman, si besoin

Si vous souhaitez automatiser le test, vous pouvez enregistrer le token dans une variable d’environnement Postman lors de la requête /login, puis l’utiliser dans la requête /protected.

Voici comment faire :

  1. Dans la requête /login, allez dans l'onglet Tests et ajoutez ce code :

    const jsonData = pm.response.json();
    pm.environment.set("jwt_token", jsonData.token);
    • Cela sauvegarde le token dans une variable d'environnement jwt_token.
  2. Dans la requête /protected, remplacez le token dans l’en-tête Authorization par Bearer {{jwt_token}}.

    • Postman remplacera automatiquement {{jwt_token}} par la valeur stockée.
  3. Maintenant, vous pouvez exécuter les deux requêtes dans l'ordre sans copier manuellement le token.


Gestion des éventuelles erreurs

  • Erreur 401 Unauthorized : Cela signifie que le serveur n’a pas reçu de token JWT. Assurez-vous d’avoir ajouté le JWT dans l'en-tête Authorization avec le préfixe Bearer.
  • Erreur 403 Forbidden : Cela signifie que le token est invalide ou expiré. Si vous avez utilisé un token expiré, retournez à la route /login pour obtenir un nouveau token, puis réessayez.
  • Erreur 404 Not Found : Assurez-vous que l’URL est correcte (http://localhost:3000/login et http://localhost:3000/protected).