Blog
Architecture
9. Oktober 20259 min

Rate Limiting und JWT WebSocket: Absicherung vor dem Start

Ich habe eine Überzeugung: Sicherheit wird vor dem Start gemacht, nicht danach. Zu warten, bis man Benutzer hat, um seine App zu sichern, ist wie ein Schloss nach dem Einbruch zu installieren. Hier ist, wie ich Rate Limiting und JWT-Authentifizierung auf den WebSockets von TAMSIV implementiert habe – noch bevor ich einen einzigen Benutzer hatte.

Wichtige Punkte zum Merken:
- HTTP-Rate-Limiting (100 Anfragen/15 Min./IP) und WebSocket (10 Verbindungen/Min. + 60 Nachrichten/Min./Benutzer) schützen vor Missbrauch und unerwarteten Kosten.
- JWT-Authentifizierung ist ab der WebSocket-Verbindung erforderlich – nicht danach.
- Frühes Sichern kostet weniger Zeit und Geld als spätes Sichern.
- Jede WebSocket-Nachricht kann 3 kostenpflichtige APIs auslösen – Rate Limiting schützt dein Budget.

Warum eine App sichern, bevor man Benutzer hat?

Die kurze Antwort: wegen der Kosten. Jeder WebSocket-Aufruf in TAMSIV kann potenziell drei kostenpflichtige APIs auslösen: Deepgram für STT, OpenRouter für LLM und OpenAI für TTS. Ohne Rate Limiting könnte ein bösartiges Skript – oder sogar ein einfacher Fehler – Hunderte von Euro an Rechnungen in einer Nacht verursachen.

Die lange Antwort: wegen der Architektur. Rate Limiting nachträglich hinzuzufügen, bedeutet, Code überall zu refaktorisieren. Es von Anfang an zu tun, ist eine saubere Middleware, die sich natürlich in die Sprach-Pipeline integriert.

Und wenn du mit "das sehen wir später" für die Sicherheit beginnst, wirst du es nie tun. Das ist ein Prinzip, das ich beim Aufbau von TAMSIV als Einzelentwickler gelernt habe – jede technische Abkürzung zahlt sich hundertfach aus.

Holografischer digitaler Sicherheitsschild, der über Servern in einem modernen Rechenzentrum schwebt
Sicherheit ist keine optionale Funktion – sie ist eine architektonische Voraussetzung.

Wie implementiert man HTTP-Rate-Limiting auf Express?

Das Express-Backend von TAMSIV stellt einige REST-Endpunkte (Bilderzeugung, Push-Benachrichtigungen, Administration) bereit. Ohne Schutz sind diese Endpunkte anfällig für Brute-Force-Angriffe und DDoS.

Ich habe einen einfachen, aber effektiven Rate Limiter konfiguriert: 100 Anfragen pro 15 Minuten pro IP. Hier ist die Begründung:

  • 100 Anfragen: ausreichend für den normalen Gebrauch (ein aktiver Benutzer macht selten mehr als 20 Anfragen in 15 Minuten), mit einem Puffer für Spitzenzeiten.
  • 15 Minuten: gleitendes Fenster. Kürzer (1 Minute) wäre zu restriktiv. Länger (1 Stunde) würde zu viele missbräuchliche Anfragen durchlassen.
  • Pro IP: die einfachste und universellste Methode. Lösungen, die auf der Benutzer-ID basieren, schützen nicht vor nicht authentifizierten Benutzern.

In der Praxis lässt sich die Middleware express-rate-limit in 5 Zeilen installieren. Der Header Retry-After informiert den Client über die Wartezeit. Der HTTP-Code 429 (Too Many Requests) ist die Standardantwort. Dies ist eine OWASP-Empfehlung, die jedes Backend implementieren sollte.

Warum ist WebSocket-Rate-Limiting eine echte Herausforderung?

HTTP ist einfach. WebSocket ist eine andere Geschichte. Die Verbindung ist persistent und Nachrichten kommen kontinuierlich an. Du kannst nicht einfach "Anfragen" zählen – jedes Audiosegment erzeugt eine Nachricht, und STT sendet Zwischenergebnisse im Streaming.

Ich habe zwei Schutzebenen eingerichtet:

  1. 10 Verbindungen pro Minute pro Benutzer: verhindert das Fluten von Verbindungen. Ein normaler Benutzer öffnet 1-2 Verbindungen pro Sitzung. 10 ist ein komfortabler Puffer, der Skripte abfängt.
  2. 60 Nachrichten pro Minute pro Benutzer: begrenzt den Nachrichtendurchsatz auf einer aktiven Verbindung. Das scheint viel, aber beim Audio-Streaming kommen die Chunks schnell an.
Terminalbildschirm mit Rate-Limiting-Logs und einem Dashboard für API-Anforderungsmetriken
Die Überwachung blockierter Anfragen ermöglicht die Anpassung der Schwellenwerte an die tatsächliche Nutzung.

Warum 60 Nachrichten pro Minute? Weil während eines aktiven Sprachgesprächs der Client jede Sekunde Audio-Chunks sendet. Mit nativem STT oder Deepgram kommen Zwischenergebnisse hinzu. 60 Nachrichten lassen einen Sicherheitsspielraum, während offensichtlicher Missbrauch blockiert wird.

Die zusätzliche Schwierigkeit: Die Zählung muss pro Benutzer erfolgen, nicht pro Verbindung. Ein bösartiger Benutzer könnte 9 Verbindungen öffnen und 59 Nachrichten über jede senden, wodurch ein Rate Limit pro Verbindung umgangen würde. Die Zählung nach Benutzer-ID (aus dem JWT extrahiert) löst dieses Problem.

Wie sichert man die JWT-Authentifizierung auf WebSockets?

Die WebSocket-Authentifizierung unterscheidet sich grundlegend von HTTP. Bei HTTP sendest du das Token in jedem Request im Authorization-Header. Bei WebSocket erfolgt die Authentifizierung nur einmal, bei der Verbindung.

In TAMSIV ist das Supabase JWT-Token ab der Verbindung erforderlich:

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

Der Server überprüft das Token, bevor er die Verbindung akzeptiert. Wenn das Token ungültig, abgelaufen oder nicht vorhanden ist: Verbindung sofort abgelehnt. Keine detaillierte Fehlermeldung (um Informationslecks zu vermeiden), nur ein WebSocket-Schließcode 4401.

Warum ist das Token im Query String und nicht in einem Header? Weil die WebSocket-API des Browsers keine benutzerdefinierten Header bei der Verbindung unterstützt. Dies ist eine bekannte Einschränkung des Protokolls. Die Alternative wäre, das Token als erste Nachricht zu senden, aber das lässt ein Fenster offen, in dem die Verbindung nicht authentifiziert ist – inakzeptabel für TAMSIV.

Das Token ist auch über den Header Authorization: Bearer xxx für Clients verfügbar, die dies unterstützen (wie Node.js-Bibliotheken). Der Server akzeptiert beide Methoden.

Laptop-Bildschirm, der ein Authentifizierungsflussdiagramm mit JWT-Token-Validierung zeigt
Das JWT wird bei der Verbindung überprüft – keine Nachricht wird auf einer nicht authentifizierten Verbindung verarbeitet.

Was sind die tatsächlichen Kosten einer Sicherheitslücke für einen Einzelentwickler?

Für ein Startup oder einen Einzelentwickler ist eine Sicherheitslücke nicht nur ein technisches Problem – es ist ein finanzielles Problem. Hier sind die Szenarien, die das Rate Limiting verhindert:

  • Szenario 1 – Client-Bug: Eine Endlosschleife auf der Frontend-Seite sendet Tausende von WebSocket-Nachrichten. Ohne Rate Limiting: explosive API-Rechnung. Mit: maximal 60 Nachrichten, dann Blockierung.
  • Szenario 2 – Bösartiges Skript: Jemand verwendet die API, um Inhalte über den LLM zu generieren. Ohne Rate Limiting: unbegrenzte Nutzung auf deine Kosten. Mit: blockiert nach 100 Anfragen/15 Min.
  • Szenario 3 – Leichter DDoS: Ein Bot hämmert auf die Endpunkte. Ohne Rate Limiting: Backend überlastet, App unzugänglich. Mit: übermäßige Anfragen werden abgelehnt, bevor sie die Geschäftslogik erreichen.

Das von mir eingerichtete Admin-Benachrichtigungssystem sendet eine E-Mail über Resend, wenn ein Benutzer 80 % des Rate Limits erreicht. Das ermöglicht es mir, einzugreifen, bevor das Problem kritisch wird.

Wie testet man die Sicherheit einer Anwendung vor dem Start?

Das Sicherheitsaudit von TAMSIV umfasste mehrere Bereiche:

  1. Rate-Limiting-Verifizierung: Testskripte, die Anfragen in Bursts senden und überprüfen, ob die Blockierung beim richtigen Schwellenwert ausgelöst wird.
  2. JWT-Validierung: Tests mit abgelaufenen, fehlerhaften, modifizierten (Tampering) Tokens und Tokens von anderen Supabase-Projekten.
  3. RLS-Richtlinien: Überprüfung, ob jede Tabelle des Datenbankschemas korrekte Zugriffsrichtlinien hat. Mehr als 30 RLS-Richtlinien wurden einzeln getestet.
  4. Input-Sanitisierung: Überprüfung, ob WebSocket-Nachrichten gültig sind und Injektionen blockiert werden.
  5. CORS-Konfiguration: Nur autorisierte Domänen (tamsiv.com, Entwickler-IPs) können sich mit dem Backend verbinden.

Diese Tests sind Teil der automatisierten Testsuite des Backends. Jede Bereitstellung über railway up überprüft, ob die Sicherheit nicht beeinträchtigt wurde.

Was sind die Best Practices für die Sicherheit von WebSockets in der Produktion?

Hier ist die Checkliste, die ich für TAMSIV befolgt habe, basierend auf den OWASP-Empfehlungen:

  • Authentifizierung bei der Verbindung: JWT wird überprüft, bevor das WebSocket-Upgrade akzeptiert wird.
  • Zweistufiges Rate Limiting: Verbindungen und Nachrichten, beides pro Benutzer.
  • Nachrichtenvalidierung: Jede Nachricht wird (Format, maximale Größe, Typ) vor der Verarbeitung validiert.
  • Sicherheits-Timeouts: Inaktive Verbindungen werden nach 5 Minuten geschlossen. Der AudioPlayerService hat auch einen Timeout von 30 Sekunden für die Bereinigung.
  • Protokollierung von Anomalien: Jedes erreichte Rate Limit, jeder fehlgeschlagene Verbindungsversuch wird zur Analyse protokolliert.
  • Strenges CORS: Whitelist autorisierter Ursprünge, aktualisiert bei jeder Domänenänderung.
  • TLS obligatorisch: In der Produktion wird nur wss:// akzeptiert.

Dieser "Security-First"-Ansatz stimmt mit der Philosophie der Clean Architecture des Projekts überein. Sicherheit ist keine hinzugefügte Schicht – sie ist eine architektonische Komponente, genau wie Routing oder die Datenbank.

Welche Auswirkungen hat Rate Limiting auf die Benutzererfahrung?

Ein gut konfiguriertes Rate Limiting ist für den normalen Benutzer unsichtbar. Die Schwellenwerte sind so kalibriert, dass eine legitime Nutzung sie niemals erreicht. Aber wenn ein Benutzer sie erreicht, muss die Erfahrung klar bleiben:

  • HTTP-Seite: Code 429 mit Retry-After-Header und erklärender Nachricht.
  • WebSocket-Seite: Fehlermeldung vor dem Verbindungsabbruch.
  • Frontend-Seite: nicht-blockierende Benachrichtigung, die den Benutzer informiert, langsamer zu werden.

Das Benachrichtigungssystem verwaltet diese Fälle elegant. Der Benutzer weiß, was passiert, ohne abrupt blockiert zu werden.

FAQ

Beeinträchtigt Rate Limiting Sprachgespräche im Streaming?

Nein, bei normaler Nutzung. Der Schwellenwert von 60 Nachrichten pro Minute ist so kalibriert, dass er Audio-Streaming und Zwischenergebnisse des STT berücksichtigt. Ein Benutzer, der normal spricht, überschreitet diesen Schwellenwert niemals. Wenn der Schwellenwert erreicht wird, ist dies ein Zeichen für einen Client-Bug oder missbräuchliche Nutzung.

Wie geht TAMSIV mit abgelaufenen JWT-Tokens während einer Sitzung um?

Das Supabase JWT-Token hat eine Lebensdauer von einer Stunde. Der Frontend-Client aktualisiert das Token automatisch vor dem Ablauf. Wenn die WebSocket-Verbindung ein abgelaufenes Token verwendet, sendet der Server einen spezifischen Schließcode zurück, und der Client verbindet sich mit einem neuen Token erneut.

Ist das Rate Limiting pro Benutzer konfigurierbar?

Die Standard-Schwellenwerte gelten für alle Benutzer. Das Admin-Dashboard ermöglicht es, Benutzer zu sehen, die sich den Limits nähern, und die globalen Schwellenwerte bei Bedarf anzupassen. Eine geplante Weiterentwicklung wird differenzierte Schwellenwerte pro Abonnementplan (Free vs. Pro vs. Team) ermöglichen.

Warum nicht einen Drittanbieterdienst wie Cloudflare für das Rate Limiting verwenden?

Cloudflare verwaltet HTTP-Rate-Limiting gut, deckt aber WebSockets nicht granular ab. Das anwendungsinterne Rate Limiting in TAMSIV ermöglicht eine feine Kontrolle pro Benutzer und Nachrichtentyp, was ein externes WAF nicht bieten kann. Beide Ansätze ergänzen sich.

Wie überprüfe ich, ob das Rate Limiting korrekt funktioniert?

Das Backend enthält automatisierte Tests, die Anfragen in Bursts simulieren und überprüfen, ob die HTTP 429-Antworten beim richtigen Schwellenwert ankommen. Die Produktionsprotokolle zeigen die Anzahl der täglich blockierten Anfragen, was eine kontinuierliche Anpassung der Schwellenwerte ermöglicht.