Zum Inhalt

Auth-Stack Soll-Zustand

Source-of-Truth für den Auth-Stack der kora-Plattform. Drift- Verifikation prüft den Live-Zustand gegen dieses Dokument plus die Realm-JSON-Dateien (für deklarativ deklarierte Felder).

Aktualisierungs-Disziplin: Vor jeder Auth-Änderung wird zuerst dieses Dokument aktualisiert, danach Realm-JSON / Init-Skripte / Frontend-Konfig / NPMplus-Routing — und das scripts/verify-auth-stack.sh muss am Ende grün sein. Verzweigte Änderungen ohne Doku-Update sind der Drift-Generator, den TODO-Platform-09 schließen soll.

1. Realms

Realm Zweck Pfad-Konvention Default-Realm-Role
kora-platform Operator (AVS) + Vendor (luki-net) Auth-Surface https://auth.kora.luki-net.org/realms/kora-platform/... default-roles-kora-platform (KC-Built-In, composite)
kora-tenants Tenant-User Auth-Surface (Block 8 wird die Tenant-User-Verwaltung über die Operator-UI bauen) https://auth.kora.luki-net.org/realms/kora-tenants/... default-roles-kora-tenants (KC-Built-In, composite)

Beide Realms laufen in derselben Keycloak-Instanz (Container kora-platform-keycloak, intern Port 8080, extern via NPMplus auf auth.kora.luki-net.org). Keycloak-Version 24+ — moderne Pfad- Konvention ohne /auth/-Präfix.

2. Clients

Built-In-Clients (account, account-console, admin-cli, broker, realm-management, security-admin-console) werden von Keycloak beim Realm-Anlegen automatisch erzeugt und sind nicht im Realm-JSON deklariert. Sie sind nicht Teil des Drift-Verifikations-Scopes.

2.1 kora-platform Realm

Client-ID Type PKCE Default-Scopes (Soll) Service-Account Quelle (SoT)
kora-api Confidential, bearer-only kora-scope nein Realm-JSON
kora-api-vendor-breakglass Confidential kora-scope nein Realm-JSON
kora-api-vendor-tunnel Confidential kora-scope nein Realm-JSON
kora-platform-audit Confidential kora-scope ja (view-events realm-management Rolle) create-audit-service-account.sh (Init-Skript, nicht im JSON — TODO-Platform-05)
operator-ui Public S256 kora-scope nein Realm-JSON + create-operator-ui-client.sh (Init-Skript)

2.1.1 operator-ui — Redirect-URIs + Web-Origins

Redirect-URIs (Set, Reihenfolge irrelevant):

https://platform.kora.luki-net.org/admin/operator/auth/callback
https://platform.kora.luki-net.org/admin/operator/*
http://localhost:5174/auth/callback
http://localhost:5174/*
http://localhost:8280/admin/operator/auth/callback
http://localhost:8280/admin/operator/*

Web-Origins (Set):

https://platform.kora.luki-net.org
http://localhost:5174
http://localhost:8280

Post-Logout-Redirect-URIs (##-getrennt):

https://platform.kora.luki-net.org/admin/operator/*
https://platform.kora.luki-net.org/*
http://localhost:5174/*
http://localhost:8280/admin/operator/*

2.2 kora-tenants Realm

Client-ID Type PKCE Default-Scopes (Soll) Quelle (SoT)
kora-api Confidential, bearer-only kora-scope Realm-JSON
tenant-ui Public S256 kora-scope Realm-JSON

2.2.1 tenant-ui — Redirect-URIs + Web-Origins

Redirect-URIs:

https://platform.kora.luki-net.org/tenant/auth/callback
https://platform.kora.luki-net.org/tenant/*
http://localhost:5173/tenant/auth/callback
http://localhost:5173/tenant/*
http://localhost:8280/tenant/auth/callback
http://localhost:8280/tenant/*

Web-Origins:

https://platform.kora.luki-net.org
http://localhost:5173
http://localhost:8280

3. Client-Scopes pro Realm

Beide Realms haben dieselbe Custom-Scope-Architektur — bewusst minimal: nur eine eigene Scope (kora-scope) plus Keycloak-Built-In offline_access. OIDC-Standard-Scopes (profile, email, roles, address, phone, web-origins, acr, microprofile-jwt) existieren nicht als Client-Scope-Objekte. Diese Architektur-Entscheidung ist gewollt und Lessons-Learned aus TODO-Platform-08:

  • kora-scope enthält Mapper für preferred_username, email, realm_access.roles, aud=kora-api — alle Claims, die Frontend und Backend konsumieren
  • Frontend fragt nur scope=openid kora-scope an. openid ist der OIDC-Protokoll-Marker (id-Token-Issuance) und benötigt kein Client-Scope-Objekt; alle anderen Scope-Tokens müssen registrierte Client-Scope-Objekte sein, sonst lehnt Keycloak die Anfrage mit error=invalid_scope ab

3.1 kora-scope-Mapper

kora-platform (4 Mapper):

Mapper Type Claim Quelle id-Token access-Token userinfo
kora-username oidc-usermodel-property-mapper preferred_username user.attribute=username
kora-email oidc-usermodel-property-mapper email user.attribute=email
kora-realm-roles oidc-usermodel-realm-role-mapper realm_access.roles Realm-Rollen, multivalued
kora-audience-mapper oidc-audience-mapper aud gepinnt auf kora-api

kora-tenants (5 Mapper) — dieselben vier plus zusätzlich:

Mapper Type Claim Quelle id-Token access-Token userinfo
kora-group-path-mapper oidc-group-membership-mapper groups Group-Membership, full-path, multivalued

3.2 JWT-Claim-Inventar (Soll vs. Ist)

Aktualisiert: 2026-04-28 (Color-System-Implementation, Merge 7261426).

Soll-Zustand: Standard-OIDC-Claims plus die in §3.1 von kora-scope emittierten Custom-Claims. Ist-Zustand: aus einem frisch gemintten Token (bench-operator-admin via Password-Grant) inspiziert.

Claim Soll Ist (kora-platform) Ist (kora-tenants) Status
sub ✓ Standard-OIDC-Subject-Identifier (UUID) fehlt fehlt DRIFT — siehe TODO-Auth-NEU
preferred_username ✓ via kora-username-Mapper OK
email ✓ via kora-email-Mapper OK
realm_access.roles ✓ via kora-realm-roles-Mapper OK
groups ✓ via kora-group-path-mapper (nur kora-tenants) n/a OK
aud ✓ via kora-audience-mapper, gepinnt auf kora-api OK
sid ✓ Session-ID (KC-Built-In) OK
iss ✓ Issuer (KC-Built-In) OK
exp / iat ✓ Standard-Timing OK
azp ✓ Authorized Party (Client-ID) OK

Bekannte Drift: fehlendes sub-Claim

Konsequenzen für DB-Design:

  • Tabellen mit User-Bezug nutzen aktuell zwei Workaround-Patterns:
  • NULL-Fallback wie platform_audit_log.actor_keycloak_id UUID NULLABLE — bei fehlendem sub wird NULL eingetragen
  • Composite-PK wie user_preferences (realm, username) statt natürlicher UUID-PK (Color-System-Implementation, Migration 0008)
  • Diese Patterns sind als Workarounds dokumentiert, nicht als Soll-Architektur
  • Sobald TODO-Auth-NEU umgesetzt ist, sollten neue Tabellen direkt UUID-PK via sub-Claim nutzen
  • Bestehende Tabellen können in einer separaten Migration auf UUID-PK umgestellt werden (Daten-Migration via Username-zu-sub- Lookup pro Realm)

Verify-Mechanismus: scripts/verify-auth-stack.sh Check 58 testet die Anwesenheit des sub-Claims in einem Live- Token aus beiden Realms. Aktuell liefert er FAIL als Drift- Frühwarnung. Sobald der Mapper-Fix in Live-Keycloak deployed ist, wechselt der Check automatisch auf PASS — keine Skript-Änderung nötig.

4. Realm-Roles

4.1 kora-platform Realm-Roles (deklariert in JSON)

Rolle Composite Beschreibung
operator-admin nein AVS Operator, voll: Tenants anlegen/löschen, Pakete, Templates, System-Settings
operator-editor nein AVS Operator, eingeschränkt: Tenant-Betreuung, keine strukturellen Änderungen
operator-viewer nein AVS Operator, nur lesen: Monitoring und Support
vendor-support nein luki-net Support-Account, Read-Only auf Logs/Metriken
vendor-breakglass nein luki-net Notfall-Full-Admin, 2h-Session-Cap
vendor-tunnel nein luki-net Reverse-Tunnel-Account

KC-Built-In zusätzlich: default-roles-kora-platform (composite), offline_access, uma_authorization.

Backend-Required-Role-Set: OPERATOR_ROLES = {operator-admin, operator-editor, operator-viewer} (siehe src/kora_platform/api/dependencies/tenant_context.py:38).

4.2 kora-tenants Realm-Roles (deklariert in JSON)

Rolle Composite Beschreibung
tenant-admin nein Tenant-Verwaltung, voll
tenant-editor nein Tenant-Inhalte pflegen, keine User-Verwaltung
tenant-viewer nein Tenant-Inhalte lesen

KC-Built-In: default-roles-kora-tenants (composite).

5. Frontend-Auth-Konfiguration

5.1 operator-ui (frontend/operator-ui/src/composables/useAuth.ts)

Feld Wert Quelle
keycloakBaseUrl Default https://auth.kora.luki-net.org Default in useAuth.ts:35, env-überschreibbar via VITE_KEYCLOAK_BASE_URL
Realm kora-platform env VITE_KEYCLOAK_REALM
Client-ID operator-ui env VITE_KEYCLOAK_CLIENT_ID
Scope-Request openid kora-scope (literal, hartcodiert) useAuth.ts:login()
Code-Challenge-Method S256 (PKCE) useAuth.ts:login()
Router-Base /admin/operator/ router/index.ts: createWebHistory("/admin/operator/")
redirectUri (zum Keycloak) ${window.location.origin}/admin/operator/auth/callback useAuth.ts:35, hartcodiertes /admin/operator/-Präfix
post_login_redirect-Speicherung Pfad-relativ zur Router-Base (also /tenants statt /admin/operator/tenants) useAuth.ts:login()TODO-Platform-09 Drift #5 Fix

5.2 tenant-ui (frontend/tenant-ui/src/composables/useAuth.ts)

Feld Wert Quelle
keycloakBaseUrl Default https://auth.kora.luki-net.org Default in useAuth.ts, env-überschreibbar via VITE_KEYCLOAK_BASE_URL
Realm kora-tenants env VITE_KEYCLOAK_REALM
Client-ID tenant-ui env VITE_KEYCLOAK_CLIENT_ID
Scope-Request openid kora-scope (literal, hartcodiert) useAuth.ts:login()
Code-Challenge-Method S256 (PKCE) useAuth.ts:login()
Router-Base /tenant/ router/index.ts: createWebHistory("/tenant/")
redirectUri (zum Keycloak) ${window.location.origin}/tenant/auth/callback useAuth.ts, hartcodiertes /tenant/-Präfix
post_login_redirect-Speicherung Pfad-relativ zur Router-Base useAuth.ts:login()TODO-Platform-09 Drift #5 Fix (analog operator-ui)

6. Init-Skripte (Live-State-Reconciler)

Skript Wirkung Idempotent? Quelle
infra/keycloak/init-scripts/create-audit-service-account.sh Legt kora-platform-audit Service-Account-Client an, weist realm-management.view-events zu, regeneriert das Secret nicht bei Re-Run ja TODO-B2-03
infra/keycloak/init-scripts/create-operator-ui-client.sh Legt operator-ui Public-Client an, wenn er nicht existiert. Liest aus dem Realm-JSON. Aktuell: create-or-skip (kein Reconcile-Pattern für nachträgliche JSON-Änderungen) ja TODO-Platform-05

Source-of-Truth-Prinzip: Realm-JSON deklariert den Soll-Zustand; Init-Skripte garantieren den Ist-Zustand idempotent. Re-Import des Realm-JSON ist nach Erst-Anlage tabu (würde Confidential-Client-Secrets regenerieren).

7. NPMplus-Routing-Soll

Subdomain Stream-Target Pfad-Behandlung
auth.kora.luki-net.org kora-platform-keycloak:8080 Alle Pfade durch zur Keycloak
platform.kora.luki-net.org kora-platform-api:8080 /api/v1/* → Backend; /admin/operator/* → Frontend-Bundle (FastAPI StaticFiles); /tenant/* → Frontend-Bundle (FastAPI StaticFiles); /auth/* NICHT bedient — alle Auth-Endpoints liegen ausschließlich auf der auth.-Subdomain
docs.kora.luki-net.org kora-platform-mkdocs:8000 (nicht Auth-relevant)

HTTP-Probes (Soll):

  • https://auth.kora.luki-net.org/realms/kora-platform/.well-known/openid-configuration → 200
  • https://auth.kora.luki-net.org/realms/kora-tenants/.well-known/openid-configuration → 200
  • https://platform.kora.luki-net.org/admin/operator/ → 200 (SPA-HTML)
  • https://platform.kora.luki-net.org/tenant/ → 200 (SPA-HTML)
  • https://platform.kora.luki-net.org/auth/realms/... → 404 (Bug-Pfad, muss broken sein)

8. Test-Skripte (bewusst nicht-deklarative Reconciler)

Skript Wirkung
scripts/gen-test-tokens.sh Legt idempotent: bench-operator-admin (operator-admin-Rolle, PW bench-operator-admin-pw-1234), admin-bench-tenant-{a,b} (Tenant-User in Bench-Tenant-Gruppen, PW bench-tenant-admin-pw-1234); aktiviert + setzt PW für vendor-breakglass (PW bench-vendor-bg-pw-1234); ruft Tokens via Password-Grant ab

Test-User existieren bewusst nicht im Realm-JSON — sie sind reine Bench-User für Smoke- und E2E-Tests. Realm-JSON users[] enthält nur die deklarierten Vendor-Accounts (vendor-support, vendor-breakglass, vendor-tunnel) als disabled Soll-Stubs; gen-test-tokens.sh aktiviert vendor-breakglass zur Test-Zeit.

9. Drift-Disziplin

Der Stack hat sechs Schichten, die alle gegeneinander driften können. Source-of-Truth pro Schicht:

Schicht SoT
Realm-Konfig-Soll Dieses Dokument + Realm-JSON
Realm-Konfig-Ist Live-Keycloak
Client-Reconciler Init-Skripte (create-*.sh)
Frontend-Konfig useAuth.ts-Defaults + VITE_*-Env-Vars
NPMplus-Routing NPMplus-Konfig (außerhalb Repo) + Probes in §7
Test-User gen-test-tokens.sh

Pflicht: Bei jeder Auth-Änderung wird zuerst dieses Dokument aktualisiert, dann die jeweilige Schicht, dann scripts/verify-auth-stack.sh lokal ausgeführt — exit 0 ist die Merge-Bedingung. Im Block 14 wird das Skript in CI integriert.

10. Letzte Aktualisierung

7909ce852590aed5e65684a8dbe9ed160bf7d227 (TODO-Platform-09-Merge), 2026-04-26 — Initial-Anlage nach Konsolidierung aus TODO-Platform-05/-06/-07/-08 und Drift-#5/#6.