--- name: onyva-login description: Integriert den zentralen Onyva Dev Login (OIDC/SSO) in ein bestehendes Projekt. Nutze diesen Skill wenn jemand "onyva login integrieren", "auth integrieren" oder "/onyva-login" schreibt. --- Deine Aufgabe ist es, den zentralen Onyva Dev Login (OIDC/SSO-Server unter `https://login.onyva.dev`) in das aktuelle Projekt zu integrieren. ## Vorgehen 1. **Analysiere das Projekt zuerst** — Lies relevante Dateien (Framework, Routing, vorhandene Auth-Logik, Env-Konfiguration), bevor du Code schreibst. 2. **Frage nach fehlenden Infos** — Du brauchst: - `OIDC_CLIENT_ID` und `OIDC_CLIENT_SECRET` (aus dem Admin-Panel von login.onyva.dev) - `OIDC_REDIRECT_URI` (wohin der Auth-Server nach dem Login zurückleitet — muss im Admin-Panel eingetragen sein) - `OIDC_LOGOUT_REDIRECT_URI` (wohin nach dem zentralen Logout weitergeleitet wird) - Falls noch nicht bekannt: welche Routen geschützt sein sollen und welche öffentlich bleiben 3. **Implementiere die Integration** nach den technischen Vorgaben unten. 4. **Passe `.env.example`** (oder äquivalente Konfiguration) mit allen nötigen Variablen an. --- ## Technische Vorgaben ### Auth-Server - Issuer / Basis-URL: `https://login.onyva.dev` - Discovery: `https://login.onyva.dev/.well-known/openid-configuration` | Endpunkt | Methode | Zweck | |---|---|---| | `/oauth/authorize` | GET | Login starten | | `/oauth/token` | POST | Code gegen Tokens tauschen | | `/oauth/userinfo` | GET | Nutzerinfos per Bearer Token | | `/oauth/jwks.json` | GET | Public Keys (RS256) | | `/logout` | GET | Zentrales Logout | ### Login starten Generiere `state` (≥16 Zeichen, zufällig) und `code_verifier` (PKCE), speichere beide serverseitig in der Session. Dann Weiterleitung zu: ``` GET https://login.onyva.dev/oauth/authorize ?response_type=code &client_id={OIDC_CLIENT_ID} &redirect_uri={OIDC_REDIRECT_URI} &scope=openid profile email &state={state} &code_challenge={BASE64URL(SHA256(code_verifier))} &code_challenge_method=S256 ``` **Wichtig:** `state` muss nachweislich in der URL stehen — ohne `state` kein `state` im Callback. ### Callback verarbeiten ```python # Pseudocode — an das Framework anpassen def callback(request): # 1. State validieren if request.params.get("state") != session.pop("oauth_state", None): return error("State mismatch") # 2. Sonderfall: Auth deaktiviert if "auth_disabled" in request.params: session["user"] = {"auth_disabled": True} return redirect("/") # 3. Token-Tausch (application/x-www-form-urlencoded, kein JSON) resp = http_post("https://login.onyva.dev/oauth/token", data={ "grant_type": "authorization_code", "code": request.params["code"], "redirect_uri": OIDC_REDIRECT_URI, "client_id": OIDC_CLIENT_ID, "client_secret": OIDC_CLIENT_SECRET, "code_verifier": session.pop("pkce_verifier"), }) tokens = resp.json() # 4. Claims aus id_token lesen claims = decode_jwt(tokens["id_token"]) # Verifikation optional für interne Nutzung session["user"] = { "sub": claims["sub"], # "user:42" "email": claims["email"], "name": claims["name"], "user_type": claims["user_type"], # "employee" | "customer" "app_roles": claims["app_roles"], # ["admin"] | ["editor"] | ["viewer"] | [] } return redirect("/") ``` Nach erfolgreichem Callback niemals auf `/auth/*` zurückleiten — `return_to` immer auf `/` oder eine echte App-Seite normalisieren. ### JWT Claims ```json { "sub": "user:42", "email": "nutzer@onyva.de", "name": "Max Mustermann", "user_type": "employee", "app_roles": ["admin"], "iss": "https://login.onyva.dev", "aud": "mein-client-id", "exp": 1700000000 } ``` ### Logout ```python def logout(request): session.clear() return redirect( f"https://login.onyva.dev/logout" f"?post_logout_redirect_uri={urllib.parse.quote(OIDC_LOGOUT_REDIRECT_URI)}" ) ``` `post_logout_redirect_uri` muss absolut sein (`https://...`). Der Auth-Server löscht die zentrale Session — der Nutzer ist danach auf allen Onyva-Apps ausgeloggt. ### Auth-Mode-Verhalten | auth_mode | Auth-Server-Verhalten | Client-Verhalten | |---|---|---| | `required` | 403 wenn keine Mitgliedschaft | Fehlermeldung anzeigen | | `optional` | Code für jeden angemeldeten Nutzer; `app_roles` ggf. leer | App entscheidet selbst | | `disabled` | Sofort zurück mit `?auth_disabled=1` | Kein Login-Dialog, alle Funktionen offen | ### AUTH_ENABLED=false (Entwicklungsmodus) ```python def require_auth(request): if not AUTH_ENABLED: return {"sub": "user:0", "email": "dev@local", "name": "Dev", "user_type": "employee", "app_roles": ["admin"]} # ... normaler Flow ``` ### Env-Variablen ```env OIDC_ISSUER=https://login.onyva.dev OIDC_CLIENT_ID= # aus Admin-Panel OIDC_CLIENT_SECRET= # aus Admin-Panel OIDC_REDIRECT_URI= # muss exakt im Admin-Panel stehen; bei /api-Prefix: https://app.example.com/api/auth/callback OIDC_LOGOUT_REDIRECT_URI= # z.B. https://app.example.com/ AUTH_ENABLED=true ``` ## Wichtige Fallstricke - **State ist Pflicht** — wird er nicht mitgeschickt, kommt er nicht zurück und der Callback schlägt fehl - **Redirect URI exakt** — `/api/auth/callback` ≠ `/auth/callback`; im Admin-Panel muss genau dieselbe URL stehen - **Token-Endpunkt: Form, kein JSON** — `Content-Type: application/x-www-form-urlencoded` - **Session-Middleware** — bei Starlette/FastAPI: `itsdangerous` als Dependency erforderlich - **Öffentliche Routen ausdrücklich ausnehmen** — Upload-Links, Webhooks, Health-Checks müssen vom Auth Guard ausgeschlossen bleiben - **Kein Redirect auf Auth-Routen** — `return_to` niemals auf `/auth/*` setzen ## Schnellcheckliste - [ ] Env-Variablen konfiguriert und `.env.example` aktualisiert - [ ] Redirect URI im Admin-Panel von login.onyva.dev eingetragen - [ ] Login-Route: `state` + PKCE generiert, in Session gespeichert, in Authorize-URL enthalten - [ ] Callback-Route: `state` validiert, Token-Tausch, Claims in Session - [ ] Logout-Route: lokale Session gelöscht, Weiterleitung zu zentralem Logout - [ ] Auth Guard für geschützte Routen - [ ] Öffentliche Routen explizit ausgenommen - [ ] `AUTH_ENABLED=false` Bypass für Entwicklung - [ ] `auth_disabled=1` Callback-Fall abgedeckt