Les Tokens JWT Expliqués : Structure, Sécurité et Bonnes Pratiques
· 12 min de lecture
Les JSON Web Tokens (JWT) sont devenus la norme de facto pour l'authentification sans état dans les applications web modernes. Que vous construisiez une API REST, une architecture de microservices ou une application monopage, comprendre le fonctionnement des JWT est essentiel pour implémenter une authentification sécurisée et évolutive.
Ce guide complet détaille tout ce que vous devez savoir sur les JWT—de leur structure interne aux pratiques de sécurité prêtes pour la production. À la fin, vous comprendrez non seulement comment utiliser les JWT, mais quand les utiliser et comment éviter les pièges courants qui mènent à des vulnérabilités de sécurité.
Table des Matières
- Qu'est-ce qu'un JWT ?
- Structure JWT : En-tête, Charge Utile et Signature
- Revendications Standard (RFC 7519)
- Comment Fonctionne l'Authentification JWT
- Exemples d'Implémentation
- Bonnes Pratiques de Sécurité
- Tokens de Rafraîchissement et Rotation des Tokens
- JWT vs Authentification Basée sur les Sessions
- Erreurs Courantes à Éviter
- Où Stocker les JWT
- Questions Fréquemment Posées
- Articles Connexes
Qu'est-ce qu'un JWT ?
Un JSON Web Token (JWT) est un moyen compact et sécurisé pour les URL de représenter des revendications à transférer entre deux parties. Les revendications dans un JWT sont encodées sous forme d'objet JSON qui est signé numériquement à l'aide de JSON Web Signature (JWS).
Considérez un JWT comme un conteneur inviolable pour les informations utilisateur. Lorsqu'un utilisateur se connecte, votre serveur crée un JWT contenant son ID utilisateur, ses rôles et d'autres données pertinentes. Ce token est ensuite envoyé au client, qui l'inclut dans chaque requête ultérieure. Le serveur peut vérifier l'authenticité du token sans interroger une base de données, ce qui rend les JWT idéaux pour les architectures sans état et évolutives.
Les JWT sont définis par la RFC 7519 et sont largement pris en charge dans tous les langages de programmation et frameworks. Ils sont particulièrement populaires dans :
- Les systèmes d'authentification unique (SSO) où les utilisateurs s'authentifient une fois et accèdent à plusieurs services
- Les architectures de microservices où les services doivent vérifier l'identité de l'utilisateur sans stockage de session partagé
- Les applications mobiles où le maintien de sessions côté serveur est peu pratique
- L'authentification API où la vérification sans état améliore l'évolutivité
Conseil rapide : Utilisez notre Décodeur JWT pour inspecter et déboguer n'importe quel token JWT instantanément. C'est entièrement côté client, donc vos tokens ne quittent jamais votre navigateur.
Structure JWT : En-tête, Charge Utile et Signature
Un JWT se compose de trois parties encodées en Base64URL séparées par des points (.). Voici à quoi ressemble un vrai JWT :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Décomposition :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← En-tête
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ ← Charge utile
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← Signature
| Partie | Contient | Exemple (décodé) |
|---|---|---|
| En-tête | Algorithme + type de token | {"alg": "HS256", "typ": "JWT"} |
| Charge utile | Revendications (données utilisateur) | {"sub": "1234567890", "name": "John Doe", "iat": 1516239022} |
| Signature | Hash de vérification | HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) |
L'En-tête
L'en-tête se compose généralement de deux parties : le type de token (typ) et l'algorithme de signature (alg). Les algorithmes courants incluent :
- HS256 (HMAC avec SHA-256) : Algorithme symétrique utilisant un secret partagé
- RS256 (RSA avec SHA-256) : Algorithme asymétrique utilisant des paires de clés publique/privée
- ES256 (ECDSA avec SHA-256) : Algorithme asymétrique avec des tailles de clés plus petites
La Charge Utile
La charge utile contient les revendications—des déclarations sur une entité (généralement l'utilisateur) et des métadonnées supplémentaires. Les revendications ne sont pas chiffrées, seulement encodées, donc ne mettez jamais d'informations sensibles comme des mots de passe dans une charge utile JWT.
La Signature
La signature garantit que le token n'a pas été altéré. 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. Si quelqu'un modifie l'en-tête ou la charge utile, la vérification de la signature échouera.
Conseil pro : Générez des JWT de test avec des revendications personnalisées en utilisant notre outil Générateur JWT. Parfait pour les scénarios de développement et de test.
Revendications Standard (RFC 7519)
La RFC 7519 définit plusieurs revendications standard qui assurent l'interopérabilité entre différentes implémentations JWT. Bien que ces revendications soient optionnelles, leur utilisation correcte améliore la sécurité et la compatibilité.
| Revendication | Nom | Objectif | Exemple |
|---|---|---|---|
iss |
Émetteur | Identifie qui a créé le token | "https://auth.example.com" |
sub |
Sujet | Identifie de qui parle le token (généralement l'ID utilisateur) | "user_12345" |
aud |
Audience | Identifie à qui le token est destiné | "https://api.example.com" |
exp |
Expiration | Quand le token expire (horodatage Unix) | 1735689600 |
nbf |
Pas Avant | Le token n'est pas valide avant cette heure | 1735603200 |
iat |
Émis À | Quand le token a été créé | 1735603200 |
jti |
ID JWT | Identifiant unique pour le token (utile pour la révocation) | "a1b2c3d4-e5f6" |
Revendications Personnalisées
Au-delà des revendications standard, vous pouvez ajouter des revendications personnalisées spécifiques à votre application. Les exemples courants incluent :
roleouroles: Permissions utilisateur (par ex.,"admin","user")email: Adresse e-mail de l'utilisateurname: Nom d'affichage de l'utilisateurscope: Portées OAuth 2.0 pour l'accès APItenant_id: Identifiant d'application multi-locataire
Gardez votre charge utile petite—chaque octet ajoute à la surcharge réseau. Incluez uniquement les revendications dont vous avez besoin pour les décisions d'autorisation. Si vous avez besoin de données utilisateur supplémentaires, récupérez-les depuis votre base de données en utilisant la revendication sub comme clé de recherche.
Comment Fonctionne l'Authentification JWT
Comprendre le flux d'authentification complet vous aide à implémenter les JWT correctement et en toute sécurité. Voici la séquence typique :
- Connexion Utilisateur : L'utilisateur envoie ses identifiants (nom d'utilisateur + mot de passe) à votre point de terminaison d'authentification
- Vérification des Identifiants : Le serveur valide les identifiants par rapport à votre base de données utilisateur
- Génération du Token : Le serveur crée un JWT contenant les revendications utilisateur et le signe avec une clé secrète
- Livraison du Token : Le serveur renvoie le JWT au client (généralement dans le corps de la réponse ou comme cookie httpOnly)
- Stockage du Token : Le client stocke le JWT en toute sécurité (plus d'informations dans la section Options de Stockage)
- Requêtes Authentifiées : Le client inclut le JWT dans l'en-tête
Authorizationpour les requêtes ultérieures - Vérification du Token : Le serveur vérifie la signature et vérifie l'expiration—aucune recherche en base de données nécessaire
- Accès Accordé : Si valide, le serveur traite la requête en utilisant les revendications du token
Voici une représentation visuelle :
┌────────┐ ┌────────┐
│ Client │ │ Serveur│
└───┬────┘ └───┬────┘
│ │
│ POST /login {username, password} │
│──────────────────────────────────────────>│
│ │
│ [Vérifier identifiants]
│ [Générer JWT]
│ │
│ 200 OK {token: "eyJhbG..."} │
│<──────────────────────────────────────────│
│ │
[Stocker token] │
│ │
│ GET /api/profile │
│ Authorization: Bearer eyJhbG... │
│──────────────────────────────────────────>│
│ │
│ [Vérifier signature]
│ [Vérifier expiration]
│ [Extraire revendications]
│ │
│ 200 OK {user data} │
│<──────────────────────────────────────────│
│ │
Conseil pro : La beauté des JWT est que l'étape 7 (vérification du token) ne nécessite pas de requête en base de données. Le serveur peut vérifier l'authenticité et extraire les informations utilisateur directement depuis le token, rendant votre API hautement évolutive.
Exemples d'Implémentation
Examinons des implémentations pratiques dans les langages de programmation et frameworks populaires.
Node.js avec jsonwebtoken
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
// Point de terminaison de connexion
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// Trouver l'utilisateur dans la base de données
const user = await User.findOne({ username });
if (!user) {
return res.status(401).json({ error: 'Identifiants invalides' });
}
// Vérifier le mot de passe
const validPassword = await bcrypt.compare(password, user.passwordHash);
if (!validPassword) {
return res.status(401).json({ error: 'Identifiants invalides' });
}
// Créer JWT
const token = jwt.sign(
{
sub: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{
expiresIn: '15m',
issuer: 'https://api.example.com',
audience: 'https://example.com'
}
);
res.json({ token });
});
// Middleware de route protégée
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: 'Aucun token fourni' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(403).json({ error: 'Token invalide ou expiré' });
}
};
// Utiliser le middleware
app.get('/api/profile', authenticateToken, (req, res) => {
res.json({ userId: req.user.sub, email: req.user.email });
});
Python avec PyJWT
import jwt
import datetime
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET_KEY = 'votre-cle-secrete'
@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
username = data.get('username')
password = data.get('password')
# Vérifier les identifiants (simplifié)
user = verify_credentials(username, password)
if not user:
return jsonify({'error': 'Identifiants invalides'}), 401
# Créer JWT
payload = {
'sub': user['id'],
'email': user['email'],
'role': user['role'],
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=15),
'iat': datetime.datetime.utcnow()
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
return jsonify({'token': token})
def token_required(f):
def decorator(*args, **kwargs):
token = request.headers.get('Authorization')
if not token:
return jsonify({'error': 'Aucun token fourni'}), 401
try:
# Supprimer le préfixe 'Bearer '
token = token.split(' ')[1]
decoded = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
request.user = decoded
except jwt.ExpiredSignatureError:
return jsonify({'error': 'Token expiré'}), 403
except jwt.InvalidTokenError:
return jsonify({'error': 'Token invalide'}), 403
return f(*args, **kwargs)
return decorator
@app.route('/api/profile')
@token_required
def profile():
return jsonify({'userId': request.user['sub']})
Go avec golang-jwt
package main
import (
"time"
"github.com/golang-jwt/jwt/v5"
)
type Claims struct {
UserID string `json:"sub"`
Email string `json:"email"`
Role string `json:"role"`
jwt.RegisteredClaims
}
func generateToken(userID, email, role string) (string, error) {
claims := Claims{
UserID: userID,
Email: emai