Published on

LiteLLM Keycloak: OIDC-SSO für das Gateway

Authors

TL;DR

LiteLLM Keycloak ist die OIDC-Anbindung des Gateways an einen eigenen Identity-Provider. Zwei getrennte Wege, beide in der LiteLLM-Doku: die Admin-UI über Generic SSO (GENERIC_CLIENT_ID, Authorization-/Token-/UserInfo-Endpoint, Redirect /sso/callback, PROXY_BASE_URL mit Protokoll) und die API über JWT (enable_jwt_auth, JWKS unter /realms/{realm}/protocol/openid-connect/certs). SSO der Admin-UI ist laut LiteLLM ab v1.76.0 für bis zu fünf Nutzer frei, darüber Enterprise. JWT-Auth verlangt LITELLM_LICENSE. Gelesen in der LiteLLM- und Keycloak-Doku am 2026-10-16. Kein Anbieter-Ranking.


Gateway und IdP sind zwei Schichten

IT-Leiter suchen „litellm keycloak“, wenn das bereits laufende Gateway sich an das Firmenverzeichnis hängen soll — nicht, wenn sie LiteLLM nur als Provider-Proxy einordnen. Die Gateway-Übersicht bleibt LiteLLM: Provider hinter einer API. Keycloak als JWT-Schicht vor beliebigen LLM-Endpunkten, ohne LiteLLM-Config: LLM-API-Sicherheit Keycloak. Entra ID statt Keycloak: Entra-ID-SSO für Open WebUI, LiteLLM und n8n. Lokale Modelle hinter demselben Proxy: LiteLLM plus Ollama Routing.

LiteLLM behandelt Keycloak nicht als Sonderfall. Die JWT-Doku sagt: jeder Issuer funktioniert, der asymmetisch signiert, JWKS veröffentlicht, kid setzt und ein stabiles iss plus die konfigurierte Audience ausstellt. Keycloak erfüllt das über die Realm-Endpunkte.

Keycloak: Client und Endpunkte

In der Keycloak-Admin-Doku unter Clients → Create client:

  1. Client type OpenID Connect belassen.
  2. Eine Client-ID vergeben, speichern.
  3. Client authentication auf ON (confidential, Client-Secret).
  4. Standard Flow an (Authorization Code).
  5. Valid Redirect URIs exakt eintragen, keine Production-Wildcard *.

Redirect für die LiteLLM-Admin-UI laut LiteLLM-SSO-Seite:

https://<ihr-proxy>/sso/callback

Beispiel der Doku für lokale Tests: http://localhost:4000/sso/callback. Zusätzlich /sso/debug/callback, wenn Sie Claims prüfen (ENABLE_SSO_DEBUG=true).

Keycloak veröffentlicht die OIDC-Pfade relativ zum Host, Realm-Name einsetzen:

/realms/{realm-name}/protocol/openid-connect/auth
/realms/{realm-name}/protocol/openid-connect/token
/realms/{realm-name}/protocol/openid-connect/userinfo
/realms/{realm-name}/protocol/openid-connect/certs
/realms/{realm-name}/protocol/openid-connect/logout

Discovery: https://<keycloak>/realms/<realm>/.well-known/openid-configuration. Issuer ist https://<keycloak>/realms/<realm> — Schrägstrich und http/https müssen bytegenau zum Token passen.

Das Secret steht im Tab Credentials. Die Keycloak-Doku: Lesen über die Admin-API braucht manage-clients; view-clients sieht nur einen Platzhalter.

Admin-UI: Generic SSO

LiteLLM hat keinen Keycloak-Schalter. Der Weg ist der Generic OAuth-Client. Pflichtvariablen der SSO-Doku:

GENERIC_CLIENT_ID="<client-id>"
GENERIC_CLIENT_SECRET="<client-secret>"
GENERIC_AUTHORIZATION_ENDPOINT="https://<keycloak>/realms/<realm>/protocol/openid-connect/auth"
GENERIC_TOKEN_ENDPOINT="https://<keycloak>/realms/<realm>/protocol/openid-connect/token"
GENERIC_USERINFO_ENDPOINT="https://<keycloak>/realms/<realm>/protocol/openid-connect/userinfo"
PROXY_BASE_URL="https://<ihr-proxy>"

PROXY_BASE_URL braucht das Protokoll. Ohne https:// oder http:// meldet LiteLLM den redirect_uri-Fehler. PKCE, falls der Keycloak-Client S256 verlangt: GENERIC_CLIENT_USE_PKCE=true. State gegen CSRF: GENERIC_CLIENT_STATE auf eine Zufallszeichenkette.

Test laut Doku: Proxy starten, https://<proxy>/ui öffnen, SSO klicken, in Keycloak anmelden, Rücksprung prüfen. Notfall ohne SSO: /fallback/login.

Welche Claim-Felder LiteLLM liest

Standard, überschreibbar:

GENERIC_USER_ID_ATTRIBUTE=sub
GENERIC_USER_EMAIL_ATTRIBUTE=email
GENERIC_USER_DISPLAY_NAME_ATTRIBUTE=display_name
GENERIC_USER_ROLE_ATTRIBUTE=given_role
GENERIC_SCOPE="openid profile email"

GENERIC_USER_ID_ATTRIBUTE muss ein unveränderlicher Claim sein. Unset liest LiteLLM preferred_username. Ändert der Nutzer das im Profil, entsteht beim nächsten Login ein zweites Konto mit eigenen Keys und Teams. sub ist der OIDC-Standard.

Rollenwerte, die die SSO-Doku akzeptiert:

  • proxy_admin
  • proxy_admin_viewer
  • internal_user
  • internal_user_viewer

Pfade dürfen verschachtelt sein, zum Beispiel attributes.litellm_role. Zusätzliche Felder: GENERIC_USER_EXTRA_ATTRIBUTES=department,employee_id,groups. Fehlt UserInfo, Claims aus ID- und Access-Token: GENERIC_INCLUDE_TOKEN_CLAIMS=true — UserInfo hat Vorrang.

Teams aus einem Gruppen-Claim nur, wenn die Teams in LiteLLM schon existieren. Config-Feld team_ids_jwt_field (Beispiel groups). Kein Auto-Create. Debug: ENABLE_SSO_DEBUG=true, dann /sso/debug/login.

Lizenz der Admin-UI, wörtlich LiteLLM: Enterprise-Merkmal; ab v1.76.0 SSO frei bis fünf Nutzer, darüber Lizenz.

API: JWT gegen das Realm-JWKS

Das ist der zweite Weg: Anwendungen schicken ein Keycloak-JWT als Authorization: Bearer, nicht den LiteLLM-Master-Key. Die JWT-Doku und die Open-WebUI-Organisationsseite stufen das als Enterprise ein. Ohne LITELLM_LICENSE lehnt der Proxy mit JWT Auth is an enterprise only feature ab.

export JWT_PUBLIC_KEY_URL="https://<keycloak>/realms/<realm>/protocol/openid-connect/certs"
export JWT_AUDIENCE="litellm"
export JWT_ISSUER="https://<keycloak>/realms/<realm>"

Die JWT-Doku nennt denselben Certs-Pfad explizit für Keycloak. JWT_PUBLIC_KEY_URL darf auch die Discovery-URL sein; LiteLLM folgt jwks_uri. Ohne Audience und Issuer akzeptiert der Proxy jedes Token, das der IdP für irgendeine App signiert hat — die Organisationsseite warnt davor und loggt beim ersten Start.

general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY
  enable_jwt_auth: true
  litellm_jwtauth:
    user_id_jwt_field: "email"
    user_email_jwt_field: "email"
    user_id_upsert: true
    team_ids_jwt_field: "groups"
    admin_jwt_scope: "litellm_proxy_admin"
    issuers:
      - issuer: "https://<keycloak>/realms/<realm>"
        jwks_url: "https://<keycloak>/realms/<realm>/protocol/openid-connect/certs"
        audience: "litellm"
        user_id_jwt_field: "sub"
        user_email_jwt_field: "email"

issuers scoped die Schlüsselwahl auf den Token-iss. Die JWT-Doku rät bei mehr als einem Provider zu dieser Liste und JWT_PUBLIC_KEY_URL leer zu lassen. Cache: public_key_ttl Standard 600 Sekunden, stale-Kopie danach public_key_stale_ttl Standard 3600. Transportfehler: bis zu drei Versuche, dann 30 Sekunden Sperre. Akzeptierte Algs: RS/PS/ES 256/384/512 und EdDSA; HMAC (HS*) wird abgelehnt.

Admin-Scope laut JWT-Doku: Client-Scope litellm_proxy_admin in Keycloak anlegen und im Token gewähren.

Modellzugriff über Rollen im Token beschreibt die Seite „Control Model Access with OIDC“: user_roles_jwt_field, user_allowed_roles, enforce_rbac, role_permissions. Keycloak legt Rollen oft unter resource_access.<client-id>.roles ab — dann genau diesen Pfad setzen. Die Seite mappt derzeit internal_user.

Claims, die Keycloak erst ausstellen muss

Die Open-WebUI-Organisationsseite (getestet mit Open WebUI v0.12.0, Keycloak 26.4, LiteLLM v1.105.0) nennt drei Mapper am dedizierten Client-Scope, alle „Add to access token“:

  • Group Membership, Claim groups, Full group path aus.
  • Audience, included custom audience litellm.
  • Optional User Attribute org → Claim org_id.

Keycloak 24 und neuer schreibt nur Attribute, die im Realm User Profile stehen. Sonst org_id still leer — Attribut deklarieren oder Unmanaged Attributes auf Enabled.

Access-Token-Lebensdauer laut derselben Seite mindestens 15 Minuten. Open WebUI erneuert fünf Minuten vor Ablauf; der Keycloak-Default von fünf Minuten trifft jedes Request. Realm- oder Client-Override: Access Token Lifespan in der Keycloak-Doku (Advanced Settings).

Direktzugriff (Password Grant) nur zum Claim-Check, danach in der Capability Config wieder aus. Decode vor dem Rollout:

printf '%s' "$TOKEN" | cut -d. -f2 | tr '_-' '/+' \
  | awk '{ p=(4-length($0)%4)%4; printf "%s",$0; for(i=0;i<p;i++) printf "="; print "" }' \
  | base64 -d | jq '{iss,aud,email,groups,org_id}'

iss, aud und groups müssen zu JWT_ISSUER, JWT_AUDIENCE und den vorhandenen team_ids passen. Team fehlt: 403 No teams found in token — auch Admins brauchen eine Gruppe.

Chat-UI vor dem Gateway, TLS und WebSocket: OpenWebUI Nginx Reverse Proxy. Mehrere Proxy-Instanzen: LiteLLM Cluster Setup. Port des lokalen Ollama-Backends: Ollama Port 11434. GPU-Klasse des Servers, unverändert mit kaufen: KI-Server kaufen.

Häufige Fehler

SymptomUrsache laut Doku
redirect_uri passt nichtURI fehlt im Client oder PROXY_BASE_URL ohne Protokoll
401 Audience / IssuerJWT_AUDIENCE oder JWT_ISSUER weicht vom decodierten Token ab
401 No matching public keykid nicht in der JWKS; nach Key-Rotation public_key_ttl abwarten
401 HMAC nicht erlaubtClient signiert mit HS*; auf RS256 stellen
401 JWT Auth is an enterprise only featureLITELLM_LICENSE fehlt
403 keine Teamsgroups fehlt oder team_id existiert nicht
org_id leerUser Profile / Unmanaged Attributes in Keycloak 24+

Häufige Fragen

Ist das dasselbe wie Entra-ID-SSO?

Nein. Entra nutzt MICROSOFT_CLIENT_ID und Graph-Gruppen. Keycloak läuft über Generic SSO plus Realm-JWKS. Die Entra-Anleitung bleibt Entra-ID-SSO.

Reicht Admin-UI-SSO ohne JWT-Lizenz?

Für die Oberfläche: ja, im Rahmen der Fünf-Nutzer-Grenze ab v1.76.0. Für Bearer-JWTs der API: nein, das ist das Enterprise-Merkmal enable_jwt_auth.

Welches Secret kommt in GENERIC_CLIENT_SECRET?

Das Client-Secret aus dem Keycloak-Tab Credentials, Client authentication ON. Nicht den LiteLLM-Master-Key.

Warum schlägt der Rücksprung fehl?

Zuerst Redirect-URI und PROXY_BASE_URL inkl. Schema prüfen. Danach Assignments im Realm: Nutzer muss dem Client zugeordnet sein. Debug-Route nur temporär aktivieren.

Fazit

LiteLLM und Keycloak koppelt man in zwei dokumentierten Schritten: Generic-OIDC für /ui (GENERIC_*, /sso/callback) und JWT gegen das Realm-JWKS für die API. Claims, Audience und bestehende Teams müssen stimmen, bevor die erste Fachanwendung den Bearer schickt. Kein zweiter Hardware-Kaufguide und keine Euro-Versprechen.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen