Rate limiting e JWT WebSocket: mettere in sicurezza prima del lancio
Ho una convinzione: la sicurezza si fa prima del lancio, non dopo. Aspettare di avere utenti per proteggere la propria app è come installare una serratura dopo il furto. Ecco come ho implementato il rate limiting e l'autenticazione JWT sui WebSockets di TAMSIV — ancor prima di avere un singolo utente.
Punti chiave da ricordare:
- Il rate limiting HTTP (100 req/15min/IP) e WebSocket (10 conn/min + 60 msg/min/user) proteggono contro abusi e costi imprevisti.
- L'autenticazione JWT è richiesta fin dalla connessione WebSocket — non dopo.
- Proteggere presto costa meno in tempo e denaro che proteggere tardi.
- Ogni messaggio WebSocket può attivare 3 API a pagamento — il rate limiting protegge il tuo budget.
Perché proteggere un'app prima di avere utenti?
La risposta breve: per i costi. Ogni chiamata WebSocket in TAMSIV può potenzialmente attivare tre API a pagamento: Deepgram per lo STT, OpenRouter per il LLM, e OpenAI per il TTS. Senza rate limiting, uno script malevolo — o anche un semplice bug — potrebbe generare centinaia di euro di fattura in una notte.
La risposta lunga: per l'architettura. Aggiungere il rate limiting dopo, significa rifattorizzare codice ovunque. Farlo fin dall'inizio, è un middleware pulito che si integra naturalmente nella pipeline vocale.
E se inizi con "vedremo più tardi" per la sicurezza, non lo farai mai. Questo è un principio che ho imparato costruendo TAMSIV come sviluppatore solista — ogni scorciatoia tecnica si paga al centuplo.
Come implementare il rate limiting HTTP su Express?
Il backend Express di TAMSIV espone alcuni endpoint REST (generazione di immagini, push notifications, amministrazione). Senza protezione, questi endpoint sono vulnerabili a brute force e DDoS.
Ho configurato un rate limiter semplice ma efficace: 100 richieste ogni 15 minuti per IP. Ecco il ragionamento:
- 100 richieste: sufficienti per un uso normale (un utente attivo raramente fa più di 20 richieste in 15 minuti), con un margine per i picchi
- 15 minuti: finestra scorrevole. Più breve (1 minuto) sarebbe troppo restrittivo. Più lungo (1 ora) lascerebbe passare troppe richieste abusive.
- Per IP: il metodo più semplice e universale. Le soluzioni basate sull'ID utente non proteggono gli utenti non autenticati.
In pratica, il middleware express-rate-limit si installa in 5 righe. L'header Retry-After informa il client del tempo di attesa. Il codice HTTP 429 (Too Many Requests) è la risposta standard. È una raccomandazione OWASP che ogni backend dovrebbe implementare.
Perché il rate limiting WebSocket è una vera sfida?
L'HTTP è facile. Il WebSocket è un'altra storia. La connessione è persistente e i messaggi arrivano in flusso continuo. Non puoi semplicemente contare le "richieste" — ogni segmento audio genera un messaggio, e lo STT invia risultati intermedi in streaming.
Ho implementato due livelli di protezione:
- 10 connessioni al minuto per utente: impedisce il flood di connessioni. Un utente normale apre 1-2 connessioni per sessione. 10 è un margine confortevole che blocca gli script.
- 60 messaggi al minuto per utente: limita il flusso di messaggi su una connessione attiva. Sembra molto, ma nello streaming audio, i chunk arrivano velocemente.
Perché 60 messaggi al minuto? Perché durante una conversazione vocale attiva, il client invia chunk audio ogni secondo. Con lo STT nativo o Deepgram, i risultati intermedi si aggiungono. 60 messaggi lasciano un margine di sicurezza bloccando gli abusi evidenti.
La difficoltà aggiuntiva: il conteggio deve essere per utente, non per connessione. Un utente malevolo potrebbe aprire 9 connessioni e inviare 59 messaggi su ciascuna, aggirando un rate limit per connessione. Il conteggio per user-id (estratto dal JWT) risolve questo problema.
Come proteggere l'autenticazione JWT sui WebSockets?
L'autenticazione WebSocket è fondamentalmente diversa dall'HTTP. Con HTTP, invii il token nell'header Authorization ad ogni richiesta. Con WebSocket, l'autenticazione avviene una sola volta, alla connessione.
In TAMSIV, il token JWT Supabase è richiesto fin dalla connessione:
ws://backend:3001?token=eyJhbGciOiJIUzI1NiIs...
Il server verifica il token prima di accettare la connessione. Se il token non è valido, scaduto o assente: connessione rifiutata immediatamente. Nessun messaggio di errore dettagliato (per evitare il leaking di informazioni), solo un codice di chiusura WebSocket 4401.
Perché il token è nella query string e non in un header? Perché l'API WebSocket del browser non supporta header personalizzati alla connessione. È una limitazione nota del protocollo. L'alternativa è inviare il token come primo messaggio, ma ciò lascia una finestra in cui la connessione non è autenticata — inaccettabile per TAMSIV.
Il token è anche disponibile tramite l'header Authorization: Bearer xxx per i client che lo supportano (come le librerie Node.js). Il server accetta entrambi i metodi.
Quali sono i costi reali di una falla di sicurezza per uno sviluppatore solista?
Per una startup o uno sviluppatore solista, una falla di sicurezza non è solo un problema tecnico — è un problema finanziario. Ecco gli scenari che il rate limiting previene:
- Scenario 1 — Bug client: un loop infinito lato frontend invia migliaia di messaggi WebSocket. Senza rate limiting: fattura API esplosiva. Con: 60 messaggi max, poi blocco.
- Scenario 2 — Script malevolo: qualcuno usa l'API per generare contenuti tramite il LLM. Senza rate limiting: utilizzo illimitato a tue spese. Con: bloccato dopo 100 richieste/15min.
- Scenario 3 — DDoS leggero: un bot martella gli endpoint. Senza rate limiting: backend sovraccarico, app inaccessibile. Con: le richieste eccessive vengono rifiutate prima di raggiungere la logica di business.
Il sistema di allarmi admin che ho implementato invia un'email tramite Resend quando un utente raggiunge l'80% del rate limit. Questo mi permette di intervenire prima che il problema diventi critico.
Come testare la sicurezza di un'applicazione prima del lancio?
L'audit di sicurezza di TAMSIV ha coperto diversi aspetti:
- Verifica del rate limiting: script di test che inviano raffiche di richieste e verificano che il blocco si attivi alla soglia corretta.
- Validazione JWT: test con token scaduti, malformati, modificati (tampering) e token di altri progetti Supabase.
- Policy RLS: verifica che ogni tabella dello schema del database abbia policy di accesso corrette. Più di 30 policy RLS testate individualmente.
- Sanitizzazione dell'input: verifica che i messaggi WebSocket siano validi e che le iniezioni siano bloccate.
- Configurazione CORS: solo i domini autorizzati (tamsiv.com, IP di sviluppo) possono connettersi al backend.
Questi test fanno parte della suite di test automatizzati del backend. Ogni deployment tramite railway up verifica che la sicurezza non sia regredita.
Quali sono le buone pratiche di sicurezza per i WebSockets in produzione?
Ecco la checklist che ho seguito per TAMSIV, basata sulle raccomandazioni OWASP:
- Autenticazione alla connessione: JWT verificato prima di accettare l'upgrade WebSocket
- Rate limiting a due livelli: connessioni e messaggi, entrambi per utente
- Validazione dei messaggi: ogni messaggio è valido (formato, dimensione massima, tipo) prima dell'elaborazione
- Timeout di sicurezza: connessioni inattive chiuse dopo 5 minuti. L'AudioPlayerService ha anche un timeout di 30 secondi per la pulizia.
- Registrazione delle anomalie: ogni rate limit raggiunto, ogni tentativo di connessione fallito viene registrato per l'analisi
- CORS stretto: lista bianca di origini autorizzate, aggiornata ad ogni cambio di dominio
- TLS obbligatorio: in produzione, solo
wss://è accettato
Questo approccio "security-first" si allinea con la filosofia di architettura pulita del progetto. La sicurezza non è uno strato aggiunto — è un componente architetturale allo stesso titolo del routing o del database.
Che impatto ha il rate limiting sull'esperienza utente?
Un rate limiting ben configurato è invisibile per l'utente normale. Le soglie sono calibrate in modo che un uso legittimo non le raggiunga mai. Ma quando un utente le raggiunge, l'esperienza deve rimanere chiara:
- Lato HTTP: codice 429 con header
Retry-Aftere messaggio esplicativo - Lato WebSocket: messaggio di errore tipo prima della chiusura della connessione
- Lato frontend: notifica non bloccante che informa l'utente di rallentare
Il sistema di notifiche gestisce questi casi con grazia. L'utente sa cosa sta succedendo senza essere bloccato bruscamente.
FAQ
Il rate limiting influisce sulle conversazioni vocali in streaming?
No, per un uso normale. La soglia di 60 messaggi al minuto è calibrata per accogliere lo streaming audio e i risultati intermedi dello STT. Un utente che parla normalmente non supera mai questa soglia. Se la soglia viene raggiunta, è segno di un bug del client o di un uso abusivo.
Come gestisce TAMSIV i token JWT scaduti durante una sessione?
Il token JWT Supabase ha una durata di un'ora. Il client frontend aggiorna automaticamente il token prima della scadenza. Se la connessione WebSocket utilizza un token scaduto, il server restituisce un codice di chiusura specifico e il client si riconnette con un nuovo token.
Il rate limiting è configurabile per utente?
Le soglie predefinite si applicano a tutti gli utenti. La dashboard admin permette di vedere gli utenti che si avvicinano ai limiti e di regolare le soglie globali se necessario. Un'evoluzione prevista permetterà soglie differenziate per piano di abbonamento (Free vs Pro vs Team).
Perché non utilizzare un servizio di terze parti come Cloudflare per il rate limiting?
Cloudflare gestisce bene il rate limiting HTTP ma non copre i WebSockets in modo granulare. Il rate limiting applicativo in TAMSIV consente un controllo fine per utente e per tipo di messaggio, cosa che un WAF esterno non può offrire. I due approcci sono complementari.
Come verificare che il rate limiting funzioni correttamente?
Il backend include test automatizzati che simulano raffiche di richieste e verificano che le risposte HTTP 429 arrivino alla soglia corretta. I log di produzione mostrano il numero di richieste bloccate al giorno, il che consente di regolare le soglie continuamente.