Skip to main content

Flow JWT complet

THE HIVE utilise un schéma access + refresh token avec blacklist Redis partagée.

Caractéristiques

TokenDuréeStockage clientUsage
Access15 minMemory (JS) / SecureStorage (mobile)Header Authorization
Refresh7 joursHttpOnly cookie / SecureStorageRenouveler 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

ClaimTypeDescription
substringID utilisateur
jtistringID unique du token (blacklist key)
rolestringCANDIDAT / RECRUTEUR / ADMIN / SUPER_ADMIN
emailstringEmail utilisateur
iatintIssued at (epoch)
expintExpiration (epoch)
issstringthehive

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

ScenarioDurée max session
Utilisateur actif (refresh régulier)7 jours glissants
Utilisateur inactif7 jours depuis dernier refresh
Logout expliciteImmédiat
Changement passwordImmédiat (tous refresh révoqués)
Admin suspend userImmédiat (tous refresh révoqués + access blacklisté)