Authentification : JWT (JSON Web Token)

Les JSON Web Token (JWT) sont des jetons permettant de sécuriser les échanges de données entre parties via des signatures numériques.

Icône de calendrier
Intermédiaire
3 chapitres

Qu’est-ce qu’un JWT ?

Définition

Les JSON Web Tokens, abrégés « JWT », sont des dispositifs incontournables en ce qui concerne l’échange sécurisé de données sur le web, et plus précisément la transmission d’informations d’identification lors des processus d’authentification et d’autorisation.

Les JWT sont aujourd’hui largement utilisés pour la mise en œuvre de microservices ou d’architectures distribuées (où le frontend et le backend sont hébergés sur des serveurs distincts).

À la différence des sessions, les JWT sont utilisés dans des architectures dites « stateless », comme les API REST, où chaque requête est traitée de manière indépendante, sans que le serveur ait besoin de stocker l’état de la session de navigation.

Cela est rendu possible car les JWT contiennent en eux toutes les informations nécessaires pour authentifier et autoriser une requête.

Ainsi, les serveurs peuvent vérifier la validité d’un JWT sans avoir besoin de stocker et consulter des informations de session, ce qui simplifie le déploiement et la mise à l’échelle des applications distribuées.

Une fois que l’utilisateur se déconnecte ou que le JWT expire (après la période de validité définie), le JWT devient invalide et l’utilisateur doit se réauthentifier pour obtenir un nouveau JWT. Cela garantit la sécurité en limitant l’accès aux ressources protégées par l’authentification dans le temps.

Structure (décodée)

Un JWT est constitué de 3 composantes : l’en-tête (header), la charge utile (payload) et la signature.

En-tête (Header)

Le header définit, au sein d’un objet JSON, le type de token (typ) et l’algorithme utilisé (alg) pour signer le token.

Un exemple d’en-tête pourrait être :

copié !
{
  "alg": "HS256",
  "typ": "JWT"
}

2 algorithmes sont la plupart du temps utilisés pour signer les JWT :

  • HS256 correspond à l’algorithme de cryptographie HMAC avec un hachage SHA-256.
  • RS256 correspond à l’algorithme de cryptographie RSA avec un hachage SHA-256.

Charge utile (Payload)

En un mot, le payload représente les données transportées par le token. Ces données, aussi appelées « claims » concernent essentiellement les informations de l’utilisateur authentifié :

  • Identifiant
  • Nom d’utilisateur
  • Autorisations
  • Etc.

Elles sont aussi définies au sein d’un objet JSON et permettent de personnaliser l’expérience utilisateur, côté frontend.

Pour un utilisateur nommé Toto avec l’id 345 et le rôle admin, on aurait par exemple le payload suivant :

copié !
{
  "sub": 345,
  "iat": 1708509600,
  "exp": 1708513200,
  "nickname": "Toto",
  "admin": true
}

Les claims renseignés peuvent être :

Parmi les claims réservés, il est recommandé d’en définir au moins 3 dans son payload :

  • sub (subject) : identifiant unique du token tel qu’un numéro d’utilisateur, un email, etc.
  • iat (issued at) : indique le moment où le JWT a été émis. Cela peut être utilisé pour déterminer depuis combien de temps le token est valide ou pour d’autres contrôles de sécurité basés sur le temps. Ce claim est généralement défini automatiquement côté serveur par les bibliothèques qui implémentent l’exploitation des JWT.
  • exp (expiration time) : spécifie le moment d’expiration du JWT. Après cet instant, le JWT ne sera plus considéré comme valide et ne pourra pas être utilisé pour accéder aux ressources protégées.

Signature

La signature du token est utilisée pour vérifier que le JWT est valide.

Elle est générée en appliquant l’algorithme cryptographique (par exemple HS256 - définie dans l’en-tête) sur :

  • Le header encodé en base 64
  • Le payload encodé en base 64

Une ou plusieurs clé(s) secrète(s) doit/doivent être fournie(s) en fonction de l’algorithme de chiffrement utilisé.

Plus concrètement, voici la formule utilisée pour générer une signature en HS256 :

HS256(
  base64(header) + "." +
  base64(payload),
  cle_secrete
)

Le header et le payload, une fois encodés en base 64 sont séparés par un ..

La clé secrète est conservée côté serveur et est utilisée pour à la fois encoder et décoder les JWT. Elle ne doit jamais être transmise au client.

Structure (encodée)

Pour encoder un JWT, on concatène avec un . :

  • Son header encodé en base 64
  • Son payload encodé en base 64
  • Sa signature
base64(header).base64(payload).signature

Si l’on reprend le header et le payload précédent, on obtient donc le JWT :

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjM0NSwiaWF0IjoxNzA4NTA5NjAwLCJleHAiOjE3MDg1MTMyMDAsIm5pY2tuYW1lIjoiVG90byIsImFkbWluIjp0cnVlfQ.3Un5_wrvmH0MzSwMrs9Z57te6vB76GtWReSZjs8_dN0

La signature ci-dessus a été générée avec la clé : 3939a257017821afc405406c53cd22741720d24871e43ff24792a47045fdc083

Fonctionnement des JWT

L’authentification par JWT fonctionne en 2 temps :

  1. La création du JWT par le serveur
  2. Son exploitation par le client

Les schémas suivants détaillent chacune des ces étapes.

Création du JWT

1. Génération d’un JWT unique

Le serveur génère un JWT pour cet utilisateur.

Ce JWT contient les informations nécessaires (identité de l’utilisateur, autorisations associées…) et est signé avec une clé secrète.

Schéma d'authentification - Génération d'un JWT unique
Schéma d'authentification - Génération d'un JWT unique

2. Transmission du JWT au client

Le serveur envoie ce JWT au navigateur au sein de la réponse HTTP.

Le JWT est ensuite stocké en local. Bien que le JWT puisse être stocké à divers endroits (localStorage, sessionStorage, cookie…), il est recommandé de le stocker dans un cookie HttpOnly.

Nous aborderons en détails la sécurisation de son JWT plus tard dans ce chapitre. 👇

Schéma d'authentification - Transmission du JWT au client
Schéma d'authentification - Transmission du JWT au client

Exploitation de la session

3. Inclusion du JWT dans les requêtes

Lorsqu’il souhaite accéder à des ressources protégées, le client envoie ce JWT dans l’en-tête de la requête HTTP. Il peut s’agir :

  • D’un Bearer Token dans l’en-tête Authorization. Authorization: Bearer <token>
  • D’un cookie HttpOnly
Schéma d'authentification - Inclusion du JWT dans les requêtes
Schéma d'authentification - Inclusion du JWT dans les requêtes

4. Vérification du JWT

Le serveur extrait le JWT de l’en-tête et vérifie sa signature à l’aide de la clé secrète utilisée lors de sa création.

Il vérifie également que le token n’a pas expiré.

Schéma d'authentification - Vérification du JWT
Schéma d'authentification - Vérification du JWT

Vulnérabilités : où stocker son JWT ?

Pour être envoyé au serveur à chaque requête HTTP, un JWT nécessite d’être stocké côté client. Et comme toute donnée stockée côté client, elle est vulnérable !

Pour stocker un JWT de manière sécurisée, il y a plusieurs options, chacune ayant ses avantages et ses inconvénients.

Découvrons comment protéger notre JWT des failles XSS et des attaques CSRF.

❌ Dans le local storage / session storage

Le stockage local du navigateur est facile à utiliser puisqu’il permet de stocker indéfiniment des données relatives à un domaine. Le stockage de session est similaire au stockage local, mais les données sont supprimées lorsque la session du navigateur prend fin.

Bien que très pratiques, ces méthodes présentent des risques de sécurité.

Les données stockées dans le local storage ou session storage sont accessibles par n’importe quel script JavaScript exécuté sur votre domaine.

Un JWT stocké dedans serait alors soumis à la faille XSS puisque du code JavaScript malveillant exécuté sur la page pourrait le dérober, puis l’utiliser pour se faire passer pour l’utilisateur.

Si votre site est particulièrement sujet aux failles XSS (saisies utilisateurs), le local storage ou session storage est donc à bannir pour stocker son JWT.

Si le JWT est stocké dans un cookie « classique », il est soumis (comme pour les cookies de sessions détaillés au chapitre précédent) aux failles XSS et CSRF.

Pour limiter la faille XSS, on lui ajoute à sa création l’attribut HttpOnly, afin de le rendre inaccessible par JavaScript.

Il ne nous reste alors plus qu’à se protéger des failles XSS en implémentant un jeton CSRF, qui sera pour sa part, stocké dans le local storage.

Ce jeton permettra de s’assurer que la requête est bien envoyée de manière « consciente » par l’utilisateur.

En général, les cookies HttpOnly sont considérés comme l’option la plus sûre pour stocker les JWT, à condition de prendre des mesures pour se protéger contre les attaques CSRF.