Flow JWT complet
THE HIVE utilise un schéma access + refresh token avec blacklist Redis partagée.
Caractéristiques
| Token | Durée | Stockage client | Usage |
|---|
| Access | 15 min | Memory (JS) / SecureStorage (mobile) | Header Authorization |
| Refresh | 7 jours | HttpOnly cookie / SecureStorage | Renouveler l’access token |
Architecture
Login
Appel API authentifié
Refresh
Rotation du refresh token : chaque refresh génère un nouveau refresh token et invalide l’ancien. Un refresh token ne peut être utilisé qu’une fois.
Détection de replay
Si un refresh token déjà révoqué est réutilisé, tous les refresh tokens du user sont révoqués en cascade et un incident est loggé.
Logout
Structure du JWT
{
"header": {
"alg": "HS256",
"typ": "JWT"
},
"payload": {
"sub": "42",
"jti": "e4f1a3b0-1234-5678-9abc-def012345678",
"role": "CANDIDAT",
"email": "jean@example.com",
"iat": 1713454200,
"exp": 1713455100,
"iss": "thehive"
}
}
Claims
| Claim | Type | Description |
|---|
sub | string | ID utilisateur |
jti | string | ID unique du token (blacklist key) |
role | string | CANDIDAT / RECRUTEUR / ADMIN / SUPER_ADMIN |
email | string | Email utilisateur |
iat | int | Issued at (epoch) |
exp | int | Expiration (epoch) |
iss | string | thehive |
Gestion côté client (JS)
class TokenManager {
constructor() {
this.access = null;
this.refresh = localStorage.getItem('refresh');
this.refreshing = null;
}
async getValidAccess() {
if (this.access && !this.isExpired(this.access)) return this.access;
if (!this.refreshing) this.refreshing = this.doRefresh();
return this.refreshing;
}
async doRefresh() {
const res = await fetch('/v1/api/auth/refresh-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken: this.refresh }),
});
if (!res.ok) { this.logout(); throw new Error('Refresh failed'); }
const { accessToken, refreshToken } = await res.json();
this.access = accessToken;
this.refresh = refreshToken;
localStorage.setItem('refresh', refreshToken);
this.refreshing = null;
return accessToken;
}
isExpired(token) {
const { exp } = JSON.parse(atob(token.split('.')[1]));
return Date.now() >= (exp - 30) * 1000;
}
}
Ne pas stocker l’access token en localStorage → vulnérable XSS. Preferer mémoire + refresh en cookie HttpOnly.
Clock skew
Le backend tolère 30 secondes d’écart entre l’horloge client et l’horloge serveur. Au-delà, le token est considéré invalide.
Horizon des sessions
| Scenario | Durée max session |
|---|
| Utilisateur actif (refresh régulier) | 7 jours glissants |
| Utilisateur inactif | 7 jours depuis dernier refresh |
| Logout explicite | Immédiat |
| Changement password | Immédiat (tous refresh révoqués) |
| Admin suspend user | Immédiat (tous refresh révoqués + access blacklisté) |