Files
onyva-login-skill/plugin/skills/onyva-login/SKILL.md

6.3 KiB

name, description
name description
onyva-login 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

# 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

{
  "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

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)

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

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 JSONContent-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-Routenreturn_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