• 1. rotation des refresh tokens OAuth2 dans un SaaS Node.js

  • 1.1. pourquoi la rotation des refresh tokens ?

  • 1.2. architecture cible (conceptuelle)

  • 1.3. prérequis et modules

  • 1.4. schéma SQL minimal pour PostgreSQL

  • 1.5. principe de fonctionnement (flow)

  • 1.6. snippet Node.js / Express (simplifié)

  • 1.7. détection de réutilisation (reuse detection)

  • 1.8. erreurs fréquentes et debugging

  • 1.9. scalabilité et performance

  • 1.10. bonnes pratiques de sécurité

  • 1.11. ressources officielles et lectures recommandées

  • 1.12. exemples concrets et métriques à surveiller

  • 1.13. conclusion et next steps

rotation des refresh tokens OAuth2 dans un SaaS Node.js : guide technique pour CTO et lead dev

Image de rotation des refresh tokens OAuth2 dans un SaaS Node.js : guide technique pour CTO et lead dev

rotation des refresh tokens OAuth2 dans un SaaS Node.js

La gestion des refresh tokens est un point critique pour la sécurité et la scalabilité d’un SaaS. Cet article explique comment implémenter une stratégie de rotation des refresh tokens dans un backend Node.js, avec des exemples concrets (schéma SQL, snippets Express, commandes CLI), les erreurs courantes et les bonnes pratiques pour détecter le vol de token.

pourquoi la rotation des refresh tokens ?

Les refresh tokens permettent de renouveler des access tokens courts sans forcer l'utilisateur à se reconnecter. Sans rotation, un refresh token volé peut être utilisé indéfiniment jusqu’à expiration. La rotation (émettre un nouveau refresh token à chaque usage et révoquer l’ancien) réduit fortement la fenêtre d’exploitation d’un token compromis et permet de détecter les réutilisations frauduleuses.

architecture cible (conceptuelle)

  • Frontend (SPA / mobile) → enregistre uniquement le refresh token en cookie HttpOnly ou storage sécurisé.
  • API Auth (Node.js + Express) → endpoints: /login, /refresh, /logout.
  • DB (PostgreSQL) → table des refresh tokens stockés sous forme hachée + métadonnées (user_id, device, expires_at, rotated_from).
  • Access tokens = JWT courts (ex. 5-15 min).

prérequis et modules

Exemple d'installation (CLI) :

npm init -y
npm install express jsonwebtoken bcryptjs pg dotenv

Vous pouvez aussi utiliser des librairies de hachage plus modernes (argon2) selon vos contraintes.

schéma SQL minimal pour PostgreSQL

-- table pour stocker les refresh tokens (hachés)
CREATE TABLE refresh_tokens (
  id BIGSERIAL PRIMARY KEY,
  user_id UUID NOT NULL,
  token_hash TEXT NOT NULL,
  device_info TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
  replaced_by BIGINT, -- id du token qui a remplacé celui-ci
  revoked BOOLEAN DEFAULT false,
  CONSTRAINT idx_token_hash UNIQUE (token_hash)
);
-- index pour rechercher par user rapidement
CREATE INDEX ON refresh_tokens (user_id);

principe de fonctionnement (flow)

  1. Utilisateur se connecte → on émet access token (court) + refresh token (long) ; on stocke le hash du refresh token en DB (ligne A).
  2. Client appelle /refresh avec refresh token → serveur :
    1. hash du token reçu → recherche en DB.
    2. si non trouvé ou revoked → rejeter et considérer possible réutilisation frauduleuse.
    3. si trouvé et valide → émettre nouveau access token et nouveau refresh token B ; marquer A.replaced_by = id(B) et stocker hash(B).
  3. Si le token A réapparaît après avoir déjà été remplacé, c’est une réutilisation (attaque) → révoquer tous les tokens de l’utilisateur et forcer logout + investigation.

snippet Node.js / Express (simplifié)

// routes/auth.js (extraits)
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const { pool } = require('./db'); // client pg
const router = express.Router();

const ACCESS_SECRET = process.env.ACCESS_SECRET;
const REFRESH_SECRET = process.env.REFRESH_SECRET;
const REFRESH_TTL_DAYS = 30;

function signAccessToken(user) {
  return jwt.sign({ sub: user.id, role: user.role }, ACCESS_SECRET, { expiresIn: '10m' });
}

function signRefreshToken() {
  // token long, aléatoire ; ici on utilise JWT pour simplicité mais peut être une random string
  return jwt.sign({ rnd: Math.random().toString(36).slice(2) }, REFRESH_SECRET, { expiresIn: `${REFRESH_TTL_DAYS}d` });
}

router.post('/refresh', async (req, res) => {
  const incoming = req.body.refreshToken;
  if (!incoming) return res.status(400).send('missing token');

  try {
    // 1) verify format (optional)
    jwt.verify(incoming, REFRESH_SECRET); // permet d'éliminer tokens blatants modifiés

    // 2) hash and search in DB
    const tokenHash = await bcrypt.hash(incoming, 10); // attention: voir remarque sur bcrypt costs
    const find = await pool.query('SELECT * FROM refresh_tokens WHERE token_hash = $1', [tokenHash]);

    if (find.rowCount === 0) {
      // token inconnu => possible réutilisation ou vol
      // action recommandée : révoquer tous les tokens de l'utilisateur si vous pouvez l'identifier, sinon alerter.
      return res.status(401).send('Invalid refresh token');
    }

    const row = find.rows[0];
    if (row.revoked || new Date(row.expires_at) < new Date()) {
      return res.status(401).send('Refresh token expired or revoked');
    }

    // 3) émettre nouveaux tokens
    const user = { id: row.user_id }; // récupérer plus d'infos si besoin
    const accessToken = signAccessToken(user);
    const newRefresh = signRefreshToken();
    const newHash = await bcrypt.hash(newRefresh, 10);

    // 4) stocker nouveau token et marquer l'ancien remplacé
    await pool.query(
      'INSERT INTO refresh_tokens (user_id, token_hash, expires_at) VALUES ($1, $2, now() + $3::interval) RETURNING id',
      [row.user_id, newHash, `${REFRESH_TTL_DAYS} days`]
    );
    await pool.query('UPDATE refresh_tokens SET replaced_by = (SELECT max(id) FROM refresh_tokens) WHERE id = $1', [row.id]);

    res.json({ accessToken, refreshToken: newRefresh });
  } catch (err) {
    console.error(err);
    return res.status(401).send('Invalid token');
  }
});

Remarque importante : l'exemple utilise bcrypt pour illustrer ; en production, préférez un hachage adapté aux tokens (ex : HMAC + secret ou argon2) et stockez une empreinte non réversible. Ne stockez jamais le refresh token en clair.

détection de réutilisation (reuse detection)

Si un token A est utilisé pour obtenir B et plus tard A est réutilisé (par un attaquant), vous devez :

  • Considérer la session compromise.
  • Révoquer tous les refresh tokens de l'utilisateur (UPDATE refresh_tokens SET revoked = true WHERE user_id = ...).
  • Notifier l’utilisateur et demander une ré-authentification.

erreurs fréquentes et debugging

  • Problème : token non trouvé même si récent — cause fréquente : hachage non déterministe (ne pas comparer bcrypt.hash(incoming) à une valeur stockée ; utilisez bcrypt.compare ou stockez HMAC si vous voulez comparaison directe). Solution : utilisez bcrypt.compare(incoming, token_hash) au lieu de recalculer hash et comparer.
  • Problème : latence DB lors de rotation massives — solution : indexer user_id, shardiser la table si besoin, ou utiliser un magasin clé-valeur pour tokens (Redis) avec persistance.
  • Problème : tokens trop volumineux dans cookies — limitez la taille et préférez cookie HttpOnly SameSite=strict pour SPAs.

scalabilité et performance

Options selon charge :

  • DB relationnelle (PostgreSQL) pour traçabilité et requêtes riches.
  • Redis comme cache TTL pour vérifier rapidement l’existence d’un token — attention à la persistance si vous devez auditer.
  • Compromis : stocker métadonnées en DB, cache en Redis pour vérifications rapides.

bonnes pratiques de sécurité

  • Ne jamais stocker refresh tokens en clair côté serveur.
  • Stocker en cookie HttpOnly + Secure + SameSite=Strict sur web ; pour mobile, utiliser secure storage.
  • Rotation systématique à chaque /refresh et révocation en cas de réutilisation.
  • Limiter la validité des access tokens (minutes) et des refresh tokens (jours).
  • Journaliser les événements de refresh (IP, user-agent, device) pour permettre la détection d'anomalies.

ressources officielles et lectures recommandées

exemples concrets et métriques à surveiller

Indicateurs à suivre après déploiement :

  • Taux d’échec de refresh (erreurs 401) — si soudain augmente, enquête sur bugs ou attaque.
  • Nombre de rotations par utilisateur par jour (indique usage et possible automatisation abusive).
  • Latence moyenne des endpoints /refresh (SLA : < 200 ms pour UX fluide).

conclusion et next steps

La rotation des refresh tokens est une mesure de sécurité efficace pour un SaaS. En implémentant : hachage sécurisé, stockage indexé, détection de réutilisation et logs, vous réduisez fortement le risque d’usurpation de session. Pour une implémentation robuste en production, testez aussi les scénarios multi-device (un utilisateur sur 5 appareils), la révocation granulaire et l'intégration avec vos procédures de support.

Besoin d’un audit ou d’un accompagnement pour sécuriser l’authentification de votre SaaS ? Consultez nos services techniques et experts Node.js et SaaS : technologies nodejs, services SaaS, ou contactez-nous pour une évaluation personnalisée.

Image de Reporting commercial : comment construire un suivi fiable quand vos données sont partout

Reporting commercial : comment construire un suivi fiable quand vos données sont partout

Reporting commercial : comment centraliser vos données dans HubSpot, fiabiliser vos indicateurs et construire un suivi clair quand Excel, emails et facturation sont dispersés.
Image de HubSpot pour les PME en 2026 : fonctionnalités, prix et retours d'expérience

HubSpot pour les PME en 2026 : fonctionnalités, prix et retours d'expérience

HubSpot pour les PME en 2026 : fonctionnalités, prix, avantages, limites et retours d'expérience pour choisir le bon plan selon votre croissance.
Image de ERP/CRM : 9 signaux que vos outils vous ralentissent (et quoi faire maintenant en 2026)

ERP/CRM : 9 signaux que vos outils vous ralentissent (et quoi faire maintenant en 2026)

9 signaux concrets que vos ERP/CRM vous freinent et, pour chacun, une action simple, non technique pour reprendre la main en quelques semaines.
DEVIS GRATUIT

Un projet en tête ? Vous avez des questions ?

Contactez nous pour recevoir un devis gratuitement, des réponses à vos questions ou une séance de consulting offerte avec l'un de nos experts :

Nous contacter