---
name: project-pwa-sw-auth-bypass
description: "PWA-Service-Worker muss server-seitige Navigationsrouten (/auth, /oauth, /commands) explizit durchlassen, sonst Login-Loop."
metadata: 
  node_type: memory
  type: project
  originSessionId: 73911b5f-10ba-449e-abaf-9db0e509132c
---

Der Service Worker (`apps/plato-customer-fe/vite.config.ts`, VitePWA/workbox) fängt per `navigateFallback: '/index.html'` + `runtimeCaching` mit `request.mode === 'navigate'` (StaleWhileRevalidate, cache `pages`) **alle** Navigationen ab. Server-seitige Routen, die NICHT zum FE gehören, müssen daher zweifach ausgenommen werden: in `navigateFallbackDenylist` UND als eigene `NetworkOnly`-Route (vor der navigate-Regel, erste passende gewinnt). Stand nach Fix `ab6933c` (2026-05-29): ausgenommen sind `/commands`, `/auth`, `/oauth`.

**Why:** Vor dem Fix war nur `/commands` ausgenommen. `/auth/login` (Navigation via `window.location.href` in FE `main.js:28`) wurde vom SW mit der gecachten index.html beantwortet statt zum IdP durchgelassen → Endlos-Loop: boot → `/auth/me` 401 → `/auth/login` → index.html → boot. Komplett client-seitig, der Server sah `/auth/login` nie. Symptom-Fingerabdruck: normales Profil loopt, Incognito beim Erststart sauber (SW kontrolliert erste Navigation noch nicht), nur "Seitendaten löschen" (= SW + Cache weg) heilt. `registerType: 'autoUpdate'` zieht den neuen SW bei Bestandsclients beim nächsten Laden nach.

**Zweiter Loop (Fix `9dd1a29`, 2026-06-02):** Die `navigate`-Runtime-Regel hat `matchOptions.ignoreSearch: true`. Lokal kehrt der IdP NICHT auf `/oauth/callback`, sondern auf die **Root** zurück (`http://localhost:3000/?code&state` — IAM hat lokal nur die Root als redirect_uri registriert, siehe `oidc.controller.ts computeRedirectUri`). Mit `ignoreSearch` matcht der Callback den gecachten `/`-Eintrag → SW liefert die alte index.html → die Root-Middleware in `main.ts` (verarbeitet `?code&state`) läuft nie → Endlos-Loop, auch nach Site-Data-Löschen. Die Denylist (`/auth`, `/oauth`) greift NICHT, weil der Pfad `/` ist. Fix: navigate-`urlPattern` um `&& !url.searchParams.has('code')` ergänzt → Callback geht ans Netz/den Server. Server-seitig war alles gesund (`/auth/login` → 302 IdP, `/auth/me` → 401) — der Bruch saß rein im SW. Diagnose-Fingerabdruck identisch (Profil loopt, Server sieht den Callback nie).

**How to apply:** Jede NEUE server-seitige Route, die der Browser per Navigation (nicht fetch) erreicht, MUSS in `navigateFallbackDenylist` UND als `NetworkOnly`-Route rein — und Callbacks, die auf der Root mit Query landen, zusätzlich aus der navigate-Regel ausnehmen (`code`-Param), sonst kehrt der Loop zurück. Verifizieren am gebauten Artefakt: `grep -o 'searchParams.has("code")' dist/apps/plato-customer-fe/sw.js`. FE muss nach Änderung neu gebaut werden (`npx nx build plato-customer-fe`), Browser einmal SW entregistrieren. Siehe [[project-run-commands]], OIDC-Flow: [[project-idp-witglobal]].
