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 :
jose(recommandée, moderne, conforme RFC)jsonwebtoken(ancienne)
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.