# Recherche: WS-Auth, Heartbeat und Reconnect Type: research Status: resolved Blocked by: — Parent: ../map.md ## Question Welche etablierten Muster gibt es für Authentifizierung, Verbindungserhalt und Reconnect bei langlebigen WebSocket-Verbindungen — und welche davon passen zu Axum 0.8 und einem JWT-basierten Auth-Modell? ### Zu klären - **Token-Übergabe beim Handshake.** Die Browser-`WebSocket`-API erlaubt keine benutzerdefinierten Header. Übliche Auswege: Token im Query-String (landet in Server-Logs — bei diesem Bedrohungsmodell heikel), `Sec-WebSocket-Protocol`-Missbrauch, Cookie, oder ein erstes Auth-Frame nach dem Handshake mit kurzem Timeout. Was ist gängige Praxis, was sind die dokumentierten Nachteile? **Achtung:** falls die Verbindung im Tauri-Rust-Layer lebt (siehe *Wo lebt die WS-Verbindung im Client*), entfällt die Header-Beschränkung komplett — die Recherche soll beide Fälle abdecken. - **Token-Ablauf bei laufender Verbindung.** Das JWT ist beim Handshake gültig und läuft nach Minuten ab, während die Verbindung Stunden steht. Muster: Verbindungs-TTL mit erzwungenem Reconnect, In-Band-Re-Auth-Frame, serverseitige Revocation-Liste. Was machen andere? - **Heartbeat.** WebSocket hat Ping/Pong auf Protokollebene. Wann reicht das, wann braucht es Application-Level-Heartbeats? Übliche Intervalle. Verhalten hinter Proxies und Corporate-Firewalls (relevant: Firmenkunden sind Zielgruppe). - **Reconnect-Strategie.** Exponentielles Backoff mit Jitter — konkrete Parameter aus der Praxis. Thundering-Herd-Verhalten, wenn ein Server neu startet und alle Clients gleichzeitig zurückkommen. - **Axum 0.8 konkret.** Wie sieht ein WebSocket-Upgrade mit State-Zugriff und Middleware aus? Greift `middleware::from_fn_with_state` beim Upgrade-Request? Wie werden viele gleichzeitige Verbindungen sauber verwaltet und beim Shutdown beendet? - **`tokio::sync::broadcast` und langsame Empfänger.** Der Kanal verwirft Nachrichten für hinterherhinkende Receiver (`RecvError::Lagged`). Wie behandelt man das korrekt, wenn „Lagged" eigentlich „Resync jetzt" bedeuten soll? Gibt es dafür ein etabliertes Muster oder bessere Kanaltypen für diesen Zweck? ### Quellenqualität Axum-/Tokio-Dokumentation und Quellcode, RFC 6455, MDN. Praxisberichte nur mit Datum. ### Erwartetes Ergebnis Eine Markdown-Datei im Repo: pro Teilfrage die Optionen mit ihren dokumentierten Vor- und Nachteilen, jeweils belegt. **Keine Empfehlung** — die Entscheidung fällt im Ticket *Verbindungsaufbau & Auth über WS*, dieses Ticket liefert nur die Faktenlage. ## Answer Vollständiges Ergebnis: [`../research/03-ws-auth-reconnect.md`](../research/03-ws-auth-reconnect.md) — Faktenlage zu allen sechs Teilfragen, Quellenverzeichnis und ein Anhang „Was nicht belegt werden konnte" mit 17 Punkten. Keine Empfehlung, wie gefordert. ### Die vier Befunde, die andere Tickets verändern **1. Der `Authorization`-Header ist der leiseste Kanal — und das ist belegbar, nicht Folklore.** In den Default-Konfigurationen von nginx, Apache und Caddy landet der Query-String im Access-Log; Caddy redigiert `Authorization` aktiv zu `REDACTED`, `request.uri` dagegen nicht. Kein Transportweg schützt vor dem Betreiber selbst — der Server *muss* das Token sehen. Der Unterschied zwischen den Optionen ist ausschließlich, was den Handshake in Logfiles, Backups und APM-Systemen **überlebt**. Nebenbei entkräftet: die verbreiteten Referrer- und Browser-History-Argumente gegen den Query-String ließen sich für `new WebSocket(url)` **nicht** belegen und gehören nicht ins Entscheidungsticket. **2. `Sec-WebSocket-Protocol` ist für dieses Bedrohungsmodell schlechter als gedacht** — Caddy loggt per Default *alle* Header außer der Redaktionsliste, und dieser gehört nicht dazu. Dazu kommt: ein JWT ist zwar ein legaler Subprotocol-Token, aber `=` ist als Separator verboten (Base64-Padding), und der Browser erzwingt ein Echo des Subprotokolls durch den Server. **3. Die Browser-API verschluckt alle Fehlerdetails — der Rust-Layer nicht.** WHATWG schreibt vor, dass DNS-Fehler, Connection-Refused, TLS-Fehler, Subprotocol-Mismatch und Abbruch nach dem Handshake für das Skript **ununterscheidbar** sein müssen: immer `CloseEvent.code === 1006`. Im WebView ist „Token abgelaufen" also nicht von „Server down" unterscheidbar. `tungstenite::Error::Http` trägt dagegen die vollständige HTTP-Response inklusive Statuscode und Body. Diese Asymmetrie zieht sich durch fast jede Teilfrage → starkes Argument in *Wo lebt die WS-Verbindung im Client*. **4. `axum::serve(...).with_graceful_shutdown(...)` beendet WebSocket-Verbindungen nicht und wartet nicht auf sie.** Belegt vierfach: der `close_rx`-Guard hängt nur an der hyper-Task, das `on_upgrade`-`tokio::spawn` nicht; `UpgradeableConnection::graceful_shutdown` ist nach dem Hijack laut hyper-util-Doku „does nothing"; axum-Issue #3003 (offen seit 21.10.2024) und PR #3831 (offen seit 11.07.2026) beschreiben genau das. **Das Projekt muss WS-Verbindungen selbst tracken und beenden** — `CancellationToken` + `TaskTracker` sind die belegten Bausteine. ### Weitere Faktenlage, kompakt - **`middleware::from_fn_with_state` greift beim Upgrade-Request** — das Upgrade ist ein normaler GET, 401/403 sind regulär sendbar. - **Token-Ablauf:** SignalR dokumentiert ausdrücklich, dass Auth **nicht** revalidiert wird. Optionen sind TTL/Reconnect (AWS API GW: 2 h / 10 min idle, hart), In-Band-Refresh (Centrifugo, Ably — Token landet dann in keinem Log) oder eine Revocation-Liste (im Betreiber-Angreifer-Modell per Definition wirkungslos). - **Heartbeat:** Protokoll-Ping ist aus dem WebView **nicht auslösbar** (WHATWG), im Rust-Layer antwortet tungstenite automatisch. Proxy-Korridor der dokumentierten Idle-Timeouts: Azure AppGW 20 s, GCP 30 s, nginx 60 s. Die verbreiteten Cloudflare-„100 s" und die HAProxy-Defaults sind Mythen und ließen sich nicht belegen. Etablierte Intervalle: Socket.IO 25 s, Phoenix 30 s, Centrifugo 25 s. - **Reconnect:** Praxiswerte reichen von 10 ms (Phoenix, ohne Jitter) bis 15 s (Ably). Gegen Thundering Herd sind vier Ansätze belegt: Client-Jitter, server-gesteuerter Hinweis (`Retry-After`, Discord Op 7), Draining und Accept-Rate-Limiting — mit Postmortems von Discord (29.04.2026) und Slack (15.03.2021). - **`RecvError::Lagged(n)`:** `n` = übersprungene Nachrichten, der Receiver bleibt brauchbar, der Cursor springt auf die älteste noch vorhandene Nachricht. ⚠️ **„Lagged = Resync" ist nirgends als offizielles Muster dokumentiert** — gängige Community-Praxis, aber ohne Primärquelle. Belegbar sind nur die Bausteine (`resubscribe()`, `Receiver::len()` als Frühwarnung, `mpsc::try_send` pro Client). - **`tokio::sync::watch` passt auffällig gut zum Dumb Pipe** — „retains only the last sent value" ist exakt die Semantik von „der Cursor der Conversation X ist jetzt N". Kein Lag-Fehler möglich. → *Fan-out-Architektur*. - **`PgListener` belegt eine dedizierte Postgres-Connection außerhalb des Pools** (aktuell `max_connections = 10`), reconnected aber selbst. `NOTIFY` ist transaktional. Neue Gefahr: eine volle Notify-Queue lässt Transaktionen **beim Commit fehlschlagen**. - **Bestandsaufnahme:** `axum = "0.8.9"` ist **ohne `ws`-Feature** eingebunden, es gibt keinen Graceful Shutdown, und `Extension` wird nirgends gelesen.