Zum Hauptinhalt springen

WebSocket hinter Cloudflare Access — Worker-JWT statt Login-302 (OP-AUTH-1 „Weg 1")

⛔ ZURÜCKGEZOGEN (2026-09-22) — „Weg 1" war eine Fehldiagnose​

Der WebSocket lief monatelang problemlos hinter der Allow-Access-App: Cloudflare Access autorisiert den eingeloggten WS am Edge und proxied ihn. Die 302, aus der dieses Dokument schloss, der WS stürbe daran, kam ausschließlich von unauthentifizierten Edge-Probes — nicht vom echten, angemeldeten Browser. Der hier beschriebene Worker-JWT-Gate auf /parties/* (aktiviert über ACCESS_TEAM_DOMAIN/ACCESS_AUD) lehnt den authentifizierten WS-Handshake mit 401 ab (das Access-JWT kommt auf dem WS-Upgrade nicht so an, wie der Gate es erwartet) → er hat die zuvor funktionierende Live-Verbindung kaputt gemacht (Regression, RISK-21). Rückzug (v0.164.1): beide Vars in server/wrangler.toml leer → der Gate ist aus → Access bleibt an (Allow = Netz-Gate, Golden Rule gewahrt), der eingeloggte WS verbindet wieder. Kein Edge-Bypass, kein Worker-Gate. Das Dokument bleibt als Lernprotokoll erhalten; der Inhalt unten ist historisch/nicht mehr gültig. Falls echte Worker-seitige WS-Identität je gebraucht wird, muss der Gate so gebaut werden, dass er WS-Upgrades niemals ablehnt (nur normale HTTP-Requests) — und erst nach Beweis am authentifizierten Pfad, nicht an unauth-Probes.


Status: historisch — Code gebaut (v0.163.0), am 2026-09-22 zurückgezogen (s. o.) · Bezug: OP-AUTH-1 · Root-CLAUDE.md → Golden Rule „Zero Trust vor Public" · server/wrangler.toml (ACCESS_TEAM_DOMAIN/ ACCESS_AUD) · Zielgruppe: IT-Ops · DevOps · CISO/Security · True North: indirekt — ohne funktionierenden WebSocket zeigt die App keine Live-Daten und beantwortet keine der drei Leitfragen.

Kernaussage. Ein WebSocket hinter einer interaktiven Cloudflare-Access-App stirbt: der unauthentifizierte WS-Upgrade bekommt einen 302→Login, dem ein WebSocket nicht folgen kann („closed before the connection is established") → keine Live-Verbindung → leere App. Lösung (Weg 1): den WS-Pfad /parties/* am Edge per Bypass freigeben (kein 302) und die Access-Identität im Worker selbst prüfen — er verifiziert das Access-JWT (aus dem CF_Authorization-Cookie, das der App-Login gesetzt hat) gegen die Team-JWKS und lehnt Unbefugte mit 401 ab. So bleibt /parties/* Zero-Trust-geschützt (Golden Rule gewahrt), aber der WS bekommt sein 101 statt eines Redirects.

1. Warum (der Befund)​

Per direktem Edge-Probe bestätigt: GET https://app.taktano.com/parties/leitstand-server/leitstand-vest mit WS-Upgrade-Headern → 302 nach drkv-com.cloudflareaccess.com/.../login (auth_status: NONE). Ein Browser-WebSocket kann einem 302 nicht folgen → er scheitert still. Die App-Shell lädt (HTML ist Access-authentifiziert), aber der WS liefert keinen State → „0 Fahrzeuge", Sichten wirken gesperrt — kein Rollen- oder App-Bug, sondern die Access-Absicherung des WS-Pfads.

2. Was der Code tut (dieser PR)​

  • server/wrangler.toml: ACCESS_TEAM_DOMAIN = "drkv-com.cloudflareaccess.com" · ACCESS_AUD = "ffafb06f…ba6" (bare Hostname; JWT-iss muss exakt https://<team>.cloudflareaccess.com sein).
  • server/party/leitstand.ts (Worker-fetch): nur für /parties/* und nur produktiv (erkannt am von Cloudflare injizierten cf-ray-Header; lokal wrangler dev/E2E fehlt er → Prüfung übersprungen) wird das Access-JWT verifiziert (verifyAccessJwt: RS256 gegen …/cdn-cgi/access/certs, exp/aud/iss geprüft). Erfolg → verifizierte E-Mail als Audit-Actor (X-Taktano-User); Misserfolg → 401.
  • Bewusst pfad-begrenzt (kleiner Blast-Radius): ein falscher ACCESS_AUD sperrt höchstens den WS aus, nie die ganze App (HTML/Assets/API bleiben von der Edge-Access-App geschützt).

3. Operator-Schritt: Edge-Bypass für /parties/* (Cloudflare Zero Trust)​

Reihenfolge zwingend — erst deployen, dann den Bypass setzen (sonst ist der WS-Pfad kurz ungeschützt):

  1. Deploy dieses Stands: npm run deploy (setzt ACCESS_TEAM_DOMAIN/ACCESS_AUD + den Worker-JWT-Check).
  2. Cloudflare Zero Trust → Access → Applications → Add an application → Self-hosted:
    • Application name: Taktano WS Bypass
    • Domain: app.taktano.com · Path: parties (deckt /parties/* ab; die spezifischere Pfad-App hat Vorrang vor der breiten app.taktano.com-App).
    • Policy: Action Bypass, Include Everyone.
    • Speichern. (Die bestehende app.taktano.com-App mit der normalen Allow-Policy bleibt — sie sorgt weiter für den interaktiven Login des App-Roots, der den CF_Authorization-Cookie setzt.)
  3. Verifizieren (angemeldet im Browser): App neu laden → DevTools-Konsole zeigt keinen WS-Fehler mehr, echte Daten erscheinen, Administration je Rolle offen. Direkt-Probe: ein WS-Upgrade auf /parties/* ohne gültige Session → 401 (nicht mehr 302); mit Session → 101.

4. Sicherheit / Golden Rule​

Der Bypass macht /parties/* am Edge offen — die Zero-Trust-Absicherung übernimmt der Worker (dieselbe Access-JWT-Prüfung, nur nicht als interaktiver Redirect). Ohne gültiges Access-JWT gibt es 401, kein Datenleck. Die Golden Rule „Zero Trust vor Public" bleibt gewahrt. Wichtig: den Bypass nie setzen, ohne dass der Worker mit ACCESS_TEAM_DOMAIN/ACCESS_AUD deployt ist (sonst wäre der WS-Pfad kurz komplett offen).

5. Rollback​

Die Bypass-Application in Cloudflare Zero Trust wieder löschen → /parties/* fällt auf den interaktiven Edge-Access zurück (WS wieder 302/gebrochen, aber sicher). Der Worker-Check schadet dann nicht (er greift nur noch auf einem nicht mehr erreichbaren Pfad). Optional die beiden Vars in wrangler.toml wieder leeren + deployen.

6. Später (OP-AUTH-1 A-3)​

Der verifizierte Better-Auth-/JWT-Claim ersetzt die interim selbst-deklarierte Rolle (Menü) durch eine verifizierte Rolle — dann trägt derselbe Worker-JWT-Pfad auch die Rollen-Durchsetzung (statt ?rolle=).


↩ Zurück zur Doku-Landkarte · Lesepfade · Register (alle OPs)