Guide complet — Gestion des Tokens sous Express.js
1. Introduction aux Tokens
Dans Express.js, un token est une preuve d’identité permettant d’autoriser un utilisateur ou un service à accéder à des ressources protégées.
Le serveur émet un token après authentification, puis le client l’envoie à chaque requête (souvent dans le header Authorization).
Les tokens remplacent les sessions classiques (cookies de session) dans les architectures modernes d’API REST ou microservices.
2. Les différentes approches modernes
A. JWT (JSON Web Token)
Un JWT est un jeton auto-contenu, signé par le serveur, qui contient des informations encodées (sub, email, role, etc.).
Bibliothèques modernes :
Structure d’un JWT :
header.payload.signature
Avantages :
- Pas de stockage côté serveur.
- Vérification rapide (signature HMAC ou RSA).
- Standard international.
Inconvénients :
- Difficile à révoquer.
- Si compromis, le token reste valide jusqu’à expiration.
- Vulnérable si stocké dans
localStorage.
B. JWT + Refresh Token
Cette approche utilise deux jetons :
- Access Token (courte durée, ex : 15 minutes)
- Refresh Token (longue durée, ex : 7 jours)
L’Access Token est stocké en mémoire, le Refresh Token en cookie HttpOnly + Secure.
Cela permet un renouvellement transparent de session sans redemander le mot de passe.
C. JWT stocké en Cookie sécurisé
Un JWT peut être stocké directement dans un cookie HttpOnly et Secure, qui remplace la session traditionnelle.
- L’auth se fait automatiquement avec le cookie.
- Le serveur vérifie le JWT sans base de données.
- Idéal pour des applications full web (SSR, Next.js, etc.)
D. OIDC / OAuth2
OpenID Connect (OIDC) et OAuth2 sont utilisés pour :
- L’authentification via tiers (Google, Microsoft, GitHub…)
- Le Single Sign-On (SSO)
- L’accès interservices sécurisé
Libs utiles :
E. Token Opaque + Redis
Le token opaque est un identifiant aléatoire sans signification :
3baf4d90-e95c-46f7-bb1f-3d27a36c6e52
Il ne contient aucune donnée utilisateur.
Le serveur stocke l’association dans Redis :
SET token:3baf4d90 { "userId": 42, "role": "admin" } EX 900
À chaque requête, le serveur consulte Redis pour vérifier la validité du token.
Avantages :
- Révocation immédiate (supprimer la clé Redis)
- Aucune donnée exposée au client
- Contrôle total côté serveur
Inconvénients :
- Redis devient un point central (stateful)
- Nécessite un accès réseau rapide et fiable
3. Comparaison entre JWT et Token Opaque
| Critère |
JWT |
Token Opaque + Redis |
| Structure |
Auto-contenu |
Identifiant aléatoire |
| Stockage |
Côté client |
Côté serveur |
| Vérification |
Locale (signature) |
Consultation Redis |
| Révocation |
Complexe |
Instantanée |
| Sécurité |
Bonne |
Très élevée |
| Scalabilité |
Excellente |
Bonne mais centralisée |
| Débogage |
Facile (lisible) |
Requiert Redis |
| Usage typique |
API publiques, microservices |
API internes, sécurité stricte |
4. Concept de Stateless vs Stateful
A. Qu’est-ce qu’une API Stateless
Une API stateless ne garde aucun état entre les requêtes.
Chaque requête contient toutes les informations nécessaires à son traitement.
Exemple :
Un JWT est stateless : il contient toutes les infos (id, rôle, expiration).
Aucun appel externe n’est nécessaire.
B. Qu’est-ce qu’une API Stateful
Une API stateful conserve l’état côté serveur (ex : Redis, sessions).
Les requêtes dépendent d’informations mémorisées sur le serveur.
Exemple :
Un token opaque nécessite de consulter Redis pour savoir à quel utilisateur il correspond.
C. Impact dans une architecture microservices
- JWT (stateless) : chaque microservice peut vérifier les tokens seul.
- Redis (stateful) : chaque service dépend du store commun.
Conséquence :
- Le modèle stateful ajoute une dépendance centrale (Redis), réduisant l’autonomie et la résilience.
- En revanche, il permet la révocation immédiate et un contrôle total.
5. Redis dans la gestion des Tokens
A. Qu’est-ce que Redis
Redis est une base clé/valeur en mémoire ultra-rapide, utilisée pour :
- le cache,
- les sessions,
- les files d’attente,
- les compteurs,
- la communication temps réel.
Redis stocke les données directement dans la RAM, ce qui le rend des dizaines de fois plus rapide que les bases de données classiques.
B. Cas d’usage concrets avec Redis
- Cache ultra-rapide
- Gestion des sessions et tokens
- File d’attente (queue)
- Rate limiting
- Pub/Sub
- Stockage temporaire
- Compteurs et statistiques
C. Pourquoi Redis est si rapide
- Données en RAM
- Opérations atomiques
- Protocole réseau minimaliste
- Mono-thread
- Optimisé pour la latence microseconde
D. Intégration Redis avec Express.js
npm install redis
Exemple simple :
const { createClient } = require("redis");
const client = createClient();
client.connect();
app.get("/", async (req, res) => {
const count = await client.incr("visits");
res.send(`Visites : ${count}`);
});
6. Bonnes pratiques de sécurité 2025
| Risque |
Mesure recommandée |
| Token volé |
Durée courte + refresh token |
| Vol cookie |
HttpOnly + Secure + SameSite=Strict |
| Secret exposé |
Stocker la clé dans .env |
| Token forgé |
Signature HMAC ou RSA |
| Révocation |
Gérer une denylist ou Redis |
| Environnement HTTP |
Toujours utiliser HTTPS |
| Vérification |
Toujours vérifier signature et expiration |
7. Exemples de code Express.js
JWT avec JOSE
import { SignJWT, jwtVerify } from "jose";
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
export async function signToken(payload, expiresIn = "15m") {
return await new SignJWT(payload)
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime(expiresIn)
.sign(secret);
}
export async function verifyToken(token) {
const { payload } = await jwtVerify(token, secret);
return payload;
}
JWT + Refresh Token en Cookie HttpOnly
router.post("/login", async (req, res) => {
const accessToken = await signToken({ sub: user.id }, "15m");
const refreshToken = await signToken({ sub: user.id, type: "refresh" }, "7d");
res.cookie("refresh_token", refreshToken, {
httpOnly: true,
secure: true,
sameSite: "Strict",
maxAge: 7 * 24 * 60 * 60 * 1000,
});
res.json({ accessToken });
});
Token Opaque + Redis
const { v4: uuidv4 } = require("uuid");
const redis = require("../utils/redis");
exports.login = (req, res) => {
User.findOne({ email: req.body.email })
.then((user) => {
if (!user) return res.status(401).json({ error: "Utilisateur introuvable" });
const token = uuidv4();
redis.set(token, JSON.stringify({ id: user._id }), { EX: 900 });
res.json({ token });
})
.catch((err) => res.status(500).json({ error: "Erreur serveur" }));
};
Fin du document.