TrendVidStream a migré toute sa pile d'authentification vers un système de jetons de renouvellement rotatifs qui détecte un jeton volé dès qu'il est réutilisé. Ce changement a transformé un JWT de 30 jours, qui permettait autrefois à un attaquant de circuler librement, en un identifiant à courte durée de vie qui déclenche instantanément un verrouillage.
Une seule faille a forcé ce changement : un SDK partenaire mettait en cache un JWT de 30 jours en texte clair ; un attaquant l'a extrait et l'a rejoué depuis un autre pays, et le seul remède a été de faire pivoter la clé de signature — une opération qui a déconnecté tous les utilisateurs. L'incident a remodelé la sécurité des jetons de l'entreprise et alimente désormais le service de streaming vidéo.
Pourquoi l'ancien modèle a échoué
Les JWT (JSON Web Tokens) sont des blocs signés et autonomes qui permettent à un serveur de vérifier une requête sans consultation de base de données. Cette commodité cache un coût : si un jeton dure des semaines, le voler donne à un adversaire des semaines d'accès. Lors de la faille, le jeton volé est resté valide jusqu'à son expiration de 30 jours car il n'y avait aucun moyen de l'invalider prématurément.
Faire pivoter la clé de signature est la seule méthode globale pour invalider tous les jetons, mais cela oblige chaque utilisateur à se reconnecter, perturbant le service et érodant la confiance. La faille ne résidait pas dans le JWT lui-même, mais dans la dépendance à un identifiant unique et de longue durée.
Le nouveau design en bref
TrendVidStream émet désormais deux types de jetons :
- Jetons d'accès (Access tokens) – valides pendant 15 minutes ; ils portent les permissions nécessaires pour chaque appel d'API.
- Jetons de renouvellement (Refresh tokens) – jetons à usage unique qui échangent un jeton d'accès à courte durée de vie contre une nouvelle paire.
Lorsqu'un client présente un jeton de renouvellement, le serveur :
- Vérifie la signature et les claims du jeton.
- Vérifie si le jeton a déjà été utilisé.
- Si la vérification réussit, émet un tout nouveau jeton de renouvellement et un nouveau jeton d'accès de 15 minutes.
- Marque l'ancien jeton de renouvellement comme utilisé.
Si l'étape 2 échoue — ce qui signifie que le même jeton apparaît une seconde fois — le serveur le traite comme un signal de vol et révoque toute la « famille » de jetons liés à cette session de connexion. La victime comme l'attaquant sont contraints de se reconnecter, interrompant ainsi la faille.
Suivi des familles de jetons
Au lieu de traiter chaque jeton comme un enregistrement isolé, le système les regroupe en familles qui débutent lors de la connexion d'un utilisateur. Chaque rotation crée un nouveau membre de cette famille. Le schéma SQLite utilisé en production stocke :
- family_id – un identifiant stable pour toute la session.
- token_id – un identifiant unique pour chaque jeton de renouvellement.
- generation – un compteur utile pour le débogage.
- used_at – l'horodatage du moment où le jeton a été utilisé.
- revoked – un indicateur qui, lorsqu'il est activé, désactive toute la famille.
Lorsqu'un événement de réutilisation est détecté, le flag revoked pour ce family_id est activé, invalidant instantanément chaque jeton appartenant à la session compromise. Cela transforme un détournement silencieux en une alerte à fort signal qui apparaît dans les journaux.
Trois règles de mise en œuvre pour garantir la fiabilité du système
Validez d'abord la signature Un attaquant pourrait deviner les ID de jetons et déclencher des révocations massives si le serveur vérifie « déjà utilisé ? » avant de vérifier la signature. Confirmer l'authenticité en premier empêche les attaques par déni de service inutiles.
Autorisez une fenêtre de grâce Les applications mobiles envoient souvent deux requêtes de renouvellement en succession rapide lorsqu'un jeton expire. Si le serveur est trop strict, la seconde requête serait signalée comme une réutilisation, déconnectant un utilisateur légitime. Quelques secondes de tampon permettent à un jeton « utilisé » de renvoyer tout de même une nouvelle paire, lissant ainsi les conditions de concurrence (race conditions).
Enveloppez tout le flux dans une transaction avec verrouillage de ligne Sans atomicité, deux requêtes concurrentes pourraient toutes deux penser être les premières à utiliser un jeton, émettant des jetons de renouvellement en double et brisant la garantie d'usage unique. Une transaction de base de données qui verrouille la ligne du jeton garantit qu'une seule requête réussit.
À retenir : En rendant les jetons de renouvellement à usage unique et en surveillant leur réutilisation, un système peut transformer chaque identifiant volé en une alarme, protégeant les utilisateurs sans imposer une déconnexion de toute la plateforme.
