Refresh Token
- sécurité
- authentification
- api
Vous vous connectez à une application. Quinze minutes plus tard, vous êtes déconnecté. "Session expirée, veuillez vous reconnecter." Frustrant. Mais si le token d'accès durait des semaines, un attaquant qui le vole aurait accès à votre compte pendant des semaines. Le Refresh Token résout ce dilemme : garder l'utilisateur connecté sans compromettre la sécurité.
Le problème : durée de vie vs sécurité
Un système d'authentification par token fonctionne en deux temps : l'utilisateur s'authentifie (login + mot de passe), et le serveur lui donne un token qu'il présente à chaque requête. Le serveur vérifie le token au lieu de redemander le mot de passe.
Mais quelle durée de vie donner à ce token ?
- Trop court (5 minutes) → l'utilisateur doit se reconnecter constamment. Expérience horrible.
- Trop long (30 jours) → si le token est volé (XSS, interception réseau, fuite de logs), l'attaquant a un accès prolongé.
La solution : deux tokens avec des rôles différents.
Access Token vs Refresh Token
| Access Token | Refresh Token | |
|---|---|---|
| Rôle | Accéder aux ressources protégées | Obtenir un nouvel Access Token |
| Durée de vie | Courte (5-15 minutes) | Longue (7-30 jours) |
| Stockage | Mémoire ou localStorage | Cookie httpOnly sécurisé |
| Envoyé à | Chaque requête API | Uniquement au endpoint de refresh |
| Si volé | Impact limité (expire vite) | Peut être révoqué côté serveur |
L'Access Token est le badge d'accès quotidien. Court, léger, utilisé partout. S'il est compromis, les dégâts sont limités par sa courte durée de vie.
Le Refresh Token est la carte d'identité stockée dans un coffre. On ne la sort que pour renouveler le badge. Il est stocké de manière sécurisée et n'est jamais envoyé aux API métier.
Le cycle de vie complet
1. LOGIN
Utilisateur → [email + mot de passe] → Serveur Auth
Serveur Auth → [Access Token + Refresh Token] → Utilisateur
2. APPELS API (pendant 15 minutes)
Utilisateur → [Access Token dans le header] → API
API → vérifie le token → renvoie les données
3. ACCESS TOKEN EXPIRÉ
Utilisateur → [Access Token expiré] → API
API → 401 Unauthorized
4. REFRESH
Utilisateur → [Refresh Token] → Serveur Auth
Serveur Auth → vérifie le Refresh Token
Serveur Auth → [Nouvel Access Token + Nouveau Refresh Token] → Utilisateur
5. REPRISE NORMALE
Utilisateur → [Nouvel Access Token] → API
API → 200 OK
Tout ce processus est transparent pour l'utilisateur. Il ne voit jamais l'écran de connexion tant que son Refresh Token est valide.
Implémentation côté client
async function fetchWithAuth(url: string, options: RequestInit = {}) {
let accessToken = getAccessToken();
let response = await fetch(url, {
...options,
headers: { ...options.headers, Authorization: `Bearer ${accessToken}` }
});
// Si le token est expiré, on le renouvelle
if (response.status === 401) {
const refreshResponse = await fetch("/auth/refresh", {
method: "POST",
credentials: "include" // Envoie le cookie httpOnly
});
if (!refreshResponse.ok) {
// Le Refresh Token est aussi expiré → redirection login
redirectToLogin();
return;
}
const { accessToken: newToken } = await refreshResponse.json();
setAccessToken(newToken);
// On rejoue la requête originale avec le nouveau token
response = await fetch(url, {
...options,
headers: { ...options.headers, Authorization: `Bearer ${newToken}` }
});
}
return response;
}Où stocker les tokens
C'est la question qui génère le plus de débats. Voici les recommandations actuelles :
Access Token → en mémoire (variable JavaScript). Pas dans le localStorage, pas dans un cookie accessible en JS. En mémoire, le token disparaît quand l'onglet se ferme et n'est pas accessible via une attaque XSS.
Refresh Token → dans un cookie httpOnly, Secure, SameSite=Strict. Ce cookie n'est pas lisible par JavaScript (protection XSS). Il n'est envoyé que vers votre domaine (protection CSRF). Il n'est transmis qu'en HTTPS.
Set-Cookie: refresh_token=abc123;
HttpOnly;
Secure;
SameSite=Strict;
Path=/auth/refresh;
Max-Age=2592000
Le Path=/auth/refresh est important : le cookie n'est envoyé qu'au endpoint de renouvellement, pas à toutes les requêtes API.
La rotation de tokens
Une bonne pratique de sécurité : à chaque utilisation du Refresh Token, le serveur en émet un nouveau et invalide l'ancien. C'est la rotation de tokens.
Pourquoi ? Si un attaquant vole un Refresh Token et l'utilise, le serveur émet un nouveau token pour l'attaquant. Quand l'utilisateur légitime essaie d'utiliser l'ancien token (désormais invalide), le serveur détecte l'anomalie et peut révoquer toute la famille de tokens, forçant une reconnexion.
Utilisation normale :
RT-1 → [refresh] → RT-2 (RT-1 invalidé)
RT-2 → [refresh] → RT-3 (RT-2 invalidé)
Attaque détectée :
Attaquant utilise RT-1 (volé) → RT-2' émis
Utilisateur utilise RT-2 → REFUSÉ (RT-2 déjà remplacé)
→ Serveur révoque toute la chaîne → Reconnexion forcée
Les erreurs courantes
Stocker le Refresh Token dans le localStorage. C'est la faille la plus répandue. Le localStorage est accessible par n'importe quel script JavaScript sur la page. Une attaque XSS suffit pour voler le token.
Ne pas implémenter la rotation. Sans rotation, un Refresh Token volé reste valide pendant toute sa durée de vie. Avec rotation, la fenêtre d'exploitation est réduite à une seule utilisation.
Ne pas prévoir la révocation. Les Refresh Tokens doivent pouvoir être révoqués côté serveur — lors d'un changement de mot de passe, d'une déconnexion explicite, ou d'une détection d'anomalie. Cela implique de les stocker en base de données.
Mettre une durée de vie trop longue sur l'Access Token. Si votre Access Token dure 24 heures, vous n'avez plus besoin de Refresh Token — et vous avez un problème de sécurité. La valeur typique est 5 à 15 minutes.
Le Refresh Token est le mécanisme qui permet de concilier sécurité et expérience utilisateur. Des Access Tokens courts pour limiter l'exposition, un Refresh Token long et sécurisé pour éviter la reconnexion. Avec la rotation et le stockage en cookie httpOnly, vous avez un système d'authentification robuste contre la majorité des attaques courantes.