---
name: project-idp-witglobal
description: "PingFederate (WITGLOBAL) als IdP für plato-customer-be — DEV-Konfiguration, BFF-Architektur, Token-Eigenschaften, Test-Rig."
metadata:
  node_type: memory
  type: project
  originSessionId: 9259f85f-e34d-4327-89e2-534bbfbc48c2
---

PingFederate (WITGLOBAL) ist der Identity Provider für die Plato-Customer-Applikation.

**Architektur (seit Umbau 2026-05-22): Backend-for-Frontend (BFF)** — passt zum ursprünglichen IAM-Ticket-Inhalt.
- BE ist OIDC-Client (`openid-client` v6, confidential client mit `client_secret`)
- BE macht Code-Tausch, behält die Tokens
- FE bekommt nur HTTP-only Session-Cookie via `express-session`
- FE redirected bei 401 (axios-Interceptor) auf `/auth/login`
- Auth-Routen: `/auth/login`, `/oauth/callback`, `/auth/me`, `/auth/logout`
- Lokal: zusätzliche Middleware in `apps/plato-customer-be/src/main.ts` fängt `GET /` mit `?code=` ab (weil IAM für localhost nur Root-Redirect-URI registriert hat)
- Server: saubere `/oauth/callback`-Route, keine Root-Middleware nötig
- Lokales Cmd: siehe [[project-run-commands]]

**DEV-Endpoints und Client:**
- Issuer: `https://login-dev.witglobal.net`
- Discovery: `https://login-dev.witglobal.net/.well-known/openid-configuration`
- Client-ID: `KONZ-PlaTo-Customer-DEV`
- Client-Secret: aus ENV `OIDC_CLIENT_SECRET` (server-side, nie im Browser-Bundle!)
- Registrierte Redirect-URIs:
  - Lokal: `http://localhost:3000` (Root, ohne Pfad — Exact-Match!)
  - DEV-Server: `https://fahrzeugeinrichtung.apps.ocp-dev01.wgn.wuerth.com/oauth/callback`
  - PROD-Server: `https://fahrzeugeinrichtung.apps.ocp-01.wgn.wuerth.com/oauth/callback`

**Token-Eigenschaften (verifiziert 2026-05-22):**
- `access_token` ist ein JWT (RS512), Audience = client_id = `KONZ-PlaTo-Customer-DEV`
- Claims im Access-Token: `scope`, `client_id`, `iat`, `jti`, `aud`, `sub`, `uid`, `iss`, `pi.sri`, `exp`
- Laufzeit: 3600s
- JWKS-URI: `https://login-dev.witglobal.net/pf/JWKS` (wird seit BFF von `openid-client` intern gehandhabt; nicht mehr direkt nötig)

**User-Mapping:** Nach erfolgreichem Code-Tausch wird `sub` (z.B. `ex08802409`) in `libs/command/command-be/src/lib/users/user.json` über `UserRegistry.getUser()` nachgeschlagen. Existiert kein Eintrag → 401. **Marcus' Eintrag `ex08802409` wurde am 2026-05-22 ergänzt** (provisorisch mit `marcus.hinz@adesso.de`). Eine richtige User-DB ist als nächstes Thema dran (siehe [[project-cleanup-timeline]]).

**Test-Rig:** `scripts/oidc-test.mjs` — Self-contained Node-Skript (keine Deps), startet HTTP-Server auf :3000, druckt Authorize-URL, fängt Callback, tauscht Code → Token, gibt alles dekodiert aus. Vor Test: Backend stoppen (Port-Konflikt). Aufruf: `CLIENT_SECRET='…' node scripts/oidc-test.mjs`. ENV überschreibbar: `CLIENT_ID`, `ISSUER`, `REDIRECT_URI`, `SCOPES`, `PORT`. Nutzt Self-Issuing-Pattern (war zum Validieren der IdP-Anbindung gedacht, vor BFF-Umbau).

**Netz-Exposure / Hardening (verifiziert 2026-05-22):** Vorgelagerter Proxy/WAF antwortet ausschließlich auf die definierten OIDC-Pfade (`/.well-known/...` → 200, `/pf/JWKS`, `/as/authorization.oauth2`, `/as/token.oauth2`). Alles andere — inkl. `GET /` und `HEAD /` — wird stillschweigend geblackholed (Connection-Timeout, kein RST, kein 403). Das ist by-design und in Ordnung. **Debugging-Falle:** Wenn die FE-PWA "trotz nicht erreichbarem IdP läuft", ist das fast immer der Service Worker + gecachte Tokens — nicht eine offene Test-Instanz. Verifikation: DevTools → Application → Clear site data → Hard-Reload. Curl aus kalter Shell ist die ehrliche Antwort auf "ist der Server wirklich offen?".

**Gotcha — `redirect_uri` Trailing-Slash bei lokalem BFF-Flow (gefixt 2026-05-22):**
PingFederate validiert `redirect_uri` beim Token-Tausch byte-exakt gegen den Authorize-Wert; bei DEV ist `http://localhost:3000` (ohne Slash) registriert und der IdP normalisiert den Authorize-Wert mit Slash intern wieder auf den registrierten String zurück. `openid-client` v6 macht beim Token-Tausch aber zwangsweise `stripParams(new URL(currentUrl))` → produziert `http://localhost:3000/` (WHATWG-URL erzwingt `pathname='/'`) → `invalid_grant: redirect_uri value must be identical to the value included in the authorization request.` **Fix:** `customFetch`-Hook in `OidcService.onModuleInit` patcht den outgoing `URLSearchParams.redirect_uri` (Body ist `URLSearchParams`, NICHT String/Uint8Array) und schneidet den Trailing-Slash ab. Server-Pfad (`/oauth/callback`) ist davon nicht betroffen.

**Offene Punkte:**
- Session-Store ist `MemoryStore` (express-session default). Für Multi-Pod-Deployment muss Redis o.ä. rein.
- `OIDC_CLIENT_SECRET` muss im Gitops-Repo als Kubernetes-Secret an den Pod gereicht werden.
