Authentification JWT : Comment Fonctionnent les JSON Web Tokens
· 12 min de lecture
Table des Matières
- Qu'est-ce qu'un JSON Web Token ?
- Structure JWT Expliquée
- Comment Fonctionne l'Authentification JWT
- Tokens d'Accès vs Tokens de Rafraîchissement
- Guide d'Implémentation Node.js
- Où Stocker les JWT en Toute Sécurité
- Meilleures Pratiques de Sécurité
- Vulnérabilités JWT Courantes
- Choisir le Bon Algorithme
- JWT dans les Systèmes Distribués
- Questions Fréquemment Posées
- Articles Connexes
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 :
- Évolutivité : Pas besoin de stockage de session ou de sessions persistantes dans les environnements à équilibrage de charge
- Authentification inter-domaines : Les JWT fonctionnent de manière transparente entre différents domaines et services
- Adapté aux mobiles : Parfait pour les applications mobiles qui doivent s'authentifier auprès des API backend
- Microservices : Chaque service peut vérifier indépendamment les tokens sans magasin de sessions central
- Performance : Élimine les recherches en base de données pour chaque requête authentifiée
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"
}
alg(algorithme) : Spécifie l'algorithme cryptographique utilisé pour signer le tokentyp(type) : Déclare qu'il s'agit d'un token 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) :
sub(sujet) : Identifiant unique pour l'utilisateuriat(émis à) : Horodatage de création du tokenexp(expiration) : Horodatage d'expiration du tokeniss(émetteur) : Identifie qui a émis le tokenaud(audience) : Identifie à qui le token est destinénbf(pas avant) : Le token n'est pas valide avant cet horodatagejti(ID JWT) : Identifiant unique pour le token lui-même
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 :
- Vérification de l'intégrité : Garantit que le token n'a pas été modifié depuis son émission
- 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 :
- Temps d'expiration court (5-60 minutes)
- Inclus dans chaque requête API
- Contient les permissions et rôles de l'utilisateur
- Ne peut pas être révoqué avant expiration (sans état)
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 :
- Temps d'expiration long (jours à mois)
- Envoyé uniquement au point de terminaison de rafraîchissement
- Peut être révoqué dans la base de données
- Souvent stocké avec des métadonnées supplémentaires (appareil, IP, agent utilisateur)
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
- Le client détecte que le token d'accès est expiré ou sur le point d'expirer
- Le client envoie le token de rafraîchissement au point de terminaison
/auth/refresh - Le serveur valide le token de rafraîchissement par rapport à la base de données
- Le serveur génère un nouveau token d'accès (et éventuellement un nouveau token de rafraîchissement)
- Le client reçoit et stocke les nouveaux tokens
- 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 });