Blog
Architecture
9 octobre 20259 min

Rate limiting et JWT WebSocket : sécuriser avant le lancement

J'ai une conviction : la sécurité se fait avant le lancement, pas après. Attendre d'avoir des utilisateurs pour sécuriser son app, c'est comme installer une serrure après le cambriolage. Voici comment j'ai implémenté le rate limiting et l'authentification JWT sur les WebSockets de TAMSIV — avant même d'avoir un seul utilisateur.

Points clés à retenir :
- Le rate limiting HTTP (100 req/15min/IP) et WebSocket (10 conn/min + 60 msg/min/user) protègent contre les abus et les coûts imprévus.
- L'authentification JWT est requise dès la connexion WebSocket — pas après.
- Sécuriser tôt coûte moins cher en temps et en argent que sécuriser tard.
- Chaque message WebSocket peut déclencher 3 APIs payantes — le rate limiting protège ton budget.

Pourquoi sécuriser une app avant d'avoir des utilisateurs ?

La réponse courte : parce que les coûts. Chaque appel WebSocket dans TAMSIV peut potentiellement déclencher trois APIs payantes : Deepgram pour le STT, OpenRouter pour le LLM, et OpenAI pour le TTS. Sans rate limiting, un script malveillant — ou même un simple bug — pourrait générer des centaines d'euros de facture en une nuit.

La réponse longue : parce que l'architecture. Ajouter le rate limiting après coup, c'est refactorer du code partout. Le faire dès le début, c'est un middleware propre qui s'intègre naturellement dans le pipeline vocal.

Et si tu commences par "on verra plus tard" pour la sécurité, tu ne le feras jamais. C'est un principe que j'ai appris en construisant TAMSIV en tant que développeur solo — chaque raccourci technique se paie au centuple.

Bouclier de sécurité numérique holographique flottant au-dessus de serveurs dans un datacenter moderne
La sécurité n'est pas une feature optionnelle — c'est un prérequis architectural.

Comment implémenter le rate limiting HTTP sur Express ?

Le backend Express de TAMSIV expose quelques endpoints REST (génération d'images, push notifications, administration). Sans protection, ces endpoints sont vulnérables au brute force et au DDoS.

J'ai configuré un rate limiter simple mais efficace : 100 requêtes par 15 minutes par IP. Voici le raisonnement :

  • 100 requêtes : suffisant pour un usage normal (un utilisateur actif fait rarement plus de 20 requêtes en 15 minutes), avec une marge pour les pics
  • 15 minutes : fenêtre glissante. Plus court (1 minute) serait trop restrictif. Plus long (1 heure) laisserait passer trop de requêtes abusives.
  • Par IP : la méthode la plus simple et la plus universelle. Les solutions basées sur le user-id ne protègent pas contre les utilisateurs non-authentifiés.

En pratique, le middleware express-rate-limit s'installe en 5 lignes. Le header Retry-After informe le client du temps d'attente. Le code HTTP 429 (Too Many Requests) est la réponse standard. C'est une recommandation OWASP que tout backend devrait implémenter.

Pourquoi le rate limiting WebSocket est-il un vrai défi ?

Le HTTP, c'est facile. Le WebSocket, c'est une autre histoire. La connexion est persistante et les messages arrivent en flux continu. Tu ne peux pas simplement compter les "requêtes" — chaque segment audio génère un message, et le STT envoie des résultats intermédiaires en streaming.

J'ai mis en place deux niveaux de protection :

  1. 10 connexions par minute par utilisateur : empêche le flood de connexions. Un utilisateur normal ouvre 1-2 connexions par session. 10 est une marge confortable qui attrape les scripts.
  2. 60 messages par minute par utilisateur : limite le débit de messages sur une connexion active. Ça semble beaucoup, mais en streaming audio, les chunks arrivent vite.
Écran de terminal affichant des logs de rate limiting et un dashboard de métriques de requêtes API
Le monitoring des requêtes bloquées permet d'ajuster les seuils en fonction de l'usage réel.

Pourquoi 60 messages par minute ? Parce que pendant une conversation vocale active, le client envoie des chunks audio toutes les secondes. Avec le STT natif ou Deepgram, les résultats intermédiaires s'ajoutent. 60 messages laissent une marge de sécurité tout en bloquant les abus évidents.

La difficulté supplémentaire : le comptage doit être par utilisateur, pas par connexion. Un utilisateur malveillant pourrait ouvrir 9 connexions et envoyer 59 messages sur chacune, contournant un rate limit par connexion. Le comptage par user-id (extrait du JWT) résout ce problème.

Comment sécuriser l'authentification JWT sur les WebSockets ?

L'authentification WebSocket est fondamentalement différente du HTTP. Avec HTTP, tu envoies le token dans le header Authorization à chaque requête. Avec WebSocket, l'authentification se fait une seule fois, à la connexion.

Dans TAMSIV, le token JWT Supabase est requis dès la connexion :

ws://backend:3001?token=eyJhbGciOiJIUzI1NiIs...

Le serveur vérifie le token avant d'accepter la connexion. Si le token est invalide, expiré ou absent : connexion refusée immédiatement. Pas de message d'erreur détaillé (pour éviter le leaking d'informations), juste un code de fermeture WebSocket 4401.

Pourquoi le token est dans le query string et pas dans un header ? Parce que l'API WebSocket du navigateur ne supporte pas les headers personnalisés à la connexion. C'est une limitation connue du protocole. L'alternative est d'envoyer le token comme premier message, mais ça laisse une fenêtre où la connexion est non-authentifiée — inacceptable pour TAMSIV.

Le token est aussi disponible via le header Authorization: Bearer xxx pour les clients qui le supportent (comme les bibliothèques Node.js). Le serveur accepte les deux méthodes.

Écran de laptop montrant un diagramme de flux d'authentification avec validation de token JWT
Le JWT est vérifié à la connexion — aucun message n'est traité sur une connexion non-authentifiée.

Quels sont les coûts réels d'une faille de sécurité pour un dev solo ?

Pour une startup ou un dev solo, une faille de sécurité n'est pas qu'un problème technique — c'est un problème financier. Voici les scénarios que le rate limiting prévient :

  • Scénario 1 — Bug client : une boucle infinie côté frontend envoie des milliers de messages WebSocket. Sans rate limiting : facture API explosive. Avec : 60 messages max, puis blocage.
  • Scénario 2 — Script malveillant : quelqu'un utilise l'API pour générer du contenu via le LLM. Sans rate limiting : utilisation illimitée à tes frais. Avec : bloqué après 100 requêtes/15min.
  • Scénario 3 — DDoS léger : un bot martèle les endpoints. Sans rate limiting : backend surchargé, app inaccessible. Avec : les requêtes excessives sont rejetées avant d'atteindre la logique métier.

Le système d'alertes admin que j'ai mis en place envoie un email via Resend quand un utilisateur atteint 80% du rate limit. Ça me permet d'intervenir avant que le problème ne devienne critique.

Comment tester la sécurité d'une application avant le lancement ?

L'audit de sécurité de TAMSIV a couvert plusieurs axes :

  1. Rate limiting verification : scripts de test qui envoient des rafales de requêtes et vérifient que le blocage se déclenche au bon seuil.
  2. JWT validation : tests avec tokens expirés, malformés, modifiés (tampering), et tokens d'autres projets Supabase.
  3. RLS policies : vérification que chaque table du schéma de base de données a des politiques d'accès correctes. Plus de 30 politiques RLS testées individuellement.
  4. Input sanitization : vérification que les messages WebSocket sont valides et que les injections sont bloquées.
  5. CORS configuration : seuls les domaines autorisés (tamsiv.com, IPs de dev) peuvent se connecter au backend.

Ces tests font partie de la suite de tests automatisés du backend. Chaque déploiement via railway up vérifie que la sécurité n'a pas régressé.

Quelles sont les bonnes pratiques de sécurité pour les WebSockets en production ?

Voici la checklist que j'ai suivie pour TAMSIV, basée sur les recommandations OWASP :

  • Authentification à la connexion : JWT vérifié avant d'accepter le upgrade WebSocket
  • Rate limiting à deux niveaux : connexions et messages, les deux par utilisateur
  • Validation des messages : chaque message est validé (format, taille maximale, type) avant traitement
  • Timeouts de sécurité : connexions inactives fermées après 5 minutes. L'AudioPlayerService a aussi un timeout de 30 secondes pour le cleanup.
  • Logging des anomalies : chaque rate limit atteint, chaque tentative de connexion échouée est logguée pour analyse
  • CORS strict : liste blanche d'origines autorisées, mise à jour à chaque changement de domaine
  • TLS obligatoire : en production, seul wss:// est accepté

Cette approche sécurité-d'abord s'aligne avec la philosophie de clean architecture du projet. La sécurité n'est pas une couche ajoutée — c'est un composant architectural au même titre que le routage ou la base de données.

Quel impact le rate limiting a-t-il sur l'expérience utilisateur ?

Un rate limiting bien configuré est invisible pour l'utilisateur normal. Les seuils sont calibrés pour qu'un usage légitime ne les atteigne jamais. Mais quand un utilisateur les atteint, l'expérience doit rester claire :

  • Côté HTTP : code 429 avec header Retry-After et message explicatif
  • Côté WebSocket : message d'erreur type avant la fermeture de connexion
  • Côté frontend : notification non-bloquante informant l'utilisateur de ralentir

Le système de notifications gère ces cas gracieusement. L'utilisateur sait ce qui se passe sans être bloqué brutalement.

FAQ

Le rate limiting affecte-t-il les conversations vocales en streaming ?

Non, pour un usage normal. Le seuil de 60 messages par minute est calibré pour accommoder le streaming audio et les résultats intermédiaires du STT. Un utilisateur qui parle normalement ne dépasse jamais ce seuil. Si le seuil est atteint, c'est le signe d'un bug client ou d'un usage abusif.

Comment TAMSIV gère-t-il les tokens JWT expirés pendant une session ?

Le token JWT Supabase a une durée de vie d'une heure. Le client frontend rafraîchit le token automatiquement avant expiration. Si la connexion WebSocket utilise un token expiré, le serveur renvoie un code de fermeture spécifique et le client se reconnecte avec un nouveau token.

Est-ce que le rate limiting est configurable par utilisateur ?

Les seuils par défaut s'appliquent à tous les utilisateurs. Le dashboard admin permet de voir les utilisateurs qui approchent des limites et d'ajuster les seuils globaux si nécessaire. Une évolution prévue permettra des seuils différenciés par plan d'abonnement (Free vs Pro vs Team).

Pourquoi ne pas utiliser un service tiers comme Cloudflare pour le rate limiting ?

Cloudflare gère bien le rate limiting HTTP mais ne couvre pas les WebSockets de manière granulaire. Le rate limiting applicatif dans TAMSIV permet un contrôle fin par utilisateur et par type de message, ce qu'un WAF externe ne peut pas offrir. Les deux approches sont complémentaires.

Comment vérifier que le rate limiting fonctionne correctement ?

Le backend inclut des tests automatisés qui simulent des rafales de requêtes et vérifient que les réponses HTTP 429 arrivent au bon seuil. Les logs de production montrent le nombre de requêtes bloquées par jour, ce qui permet d'ajuster les seuils en continu.