Skip to content

Authentifizierung einrichten ​

Der Swiss AI Hub verwendet ein Multi-Tenant-Authentifizierungs- und Autorisierungssystem mit lokaler Rollenverwaltung.

Überblick ​

Das Authentifizierungssystem besteht aus mehreren Schlüsselkomponenten:

  • Auth Handlers: Validieren Anmeldeinformationen und lösen die Benutzeridentität auf
  • Identity Models: UserIdentity und TenantIdentity repräsentieren authentifizierte Benutzer und ihren Mandantenkontext
  • Zugriffskontrolle: Der AccessChecker erzwingt Berechtigungen basierend auf hierarchischen Zugriffsregeln
  • Multi-Tenancy: Alle Operationen erfolgen innerhalb eines Mandantenkontexts

Authentifizierungsfluss ​

1. Token-Validierung ​

Auth Handler validieren eingehende Anfragen und extrahieren Benutzerinformationen:

python
# Keycloak OIDC JWT validation
user_identity = await KeycloakAuthHandler()(request)

# Token-based authentication
user_identity = await TokenAuthHandler()(request)

Unterstützte Authentifizierungsmethoden:

  • OAuth2/OIDC: JWT-Tokens von Keycloak (unterstützt föderierte Identity Provider wie Azure AD, Google usw.)
  • API-Tokens: Langlebige Tokens für programmatischen Zugriff
  • OpenWebUI-Integration: Spezieller Handler für OpenWebUI-Benutzer

Für Tests und interaktive Playground-Server befindet sich ein dedizierter TestAuthHandler unter swiss_ai_hub.core.testing.auth_utils (nicht core.auth) und umgeht die Token-Analyse, um eine feste Testidentität zurückzugeben. Er ist absichtlich nicht über die öffentliche Auth-Schnittstelle von Produktionscode aus erreichbar.

2. Benutzerauflösung ​

Benutzerprofildaten (Name, E-Mail) werden für OAuth2-Flows aus JWT-Claims gelesen oder für Bearer-Tokens über den KeycloakAdminService abgerufen. Es gibt keinen lokalen Benutzerdatensatz – Keycloak ist die zentrale Quelle der Wahrheit für die Benutzeridentität.

Verhalten des ersten Benutzers: Der erste Benutzer, der einem Mandanten beitritt, erhält automatisch Admin-Rollen. Nachfolgende Benutzer erhalten Standard-Benutzerrollen (konfigurierbar über UserSignupSettings). Dies gilt pro Mandant, nicht global, und wird in der UserTenantRoleEntity durchgesetzt, wenn eine neue Mitgliedschaft erstellt wird.

3. Mandantenkontext-Auflösung ​

Die meisten authentifizierten Anfragen haben einen Mandantenkontext. Der Mandant wird über einen {tenant_id}-Pfadparameter in der URL identifiziert – die meisten API-Routen sind unter /api/v1/{tenant_id}/... gemountet. Nur-Sysadmin-Endpunkte (z.B. der Mandantenadministrations-Controller) sind global ohne Mandantenpräfix gemountet.

Die Mandantenauflösung wird innerhalb von AuthHandler.build_identity() und AuthHandler._resolve_tenant_by_id() gehandhabt. Letzteres konsultiert zuerst KeycloakAdminService.tenant_exists() – Keycloak ist die maßgebliche Instanz dafür, ob ein Mandant existiert – und prüft erst dann die UserTenantRoleEntity-Mitgliedschaft. Die Mitgliedschaftsprüfung wird für Sysadmins vollständig übersprungen (siehe "Sysadmin-Zugriff" unten). Controller rufen diese Resolver nicht direkt auf; sie sind in user_with_permission() und sys_admin_user() verdrahtet.

Mandanten-Pfadparameter: Alle API-Anfragen müssen die {tenant_id} im URL-Pfad enthalten. Zwei Formate werden unterstützt:

  • Konkrete ID: /api/v1/507f1f77bcf86cd799439011/agents/... – gibt den Mandanten direkt über die MongoDB ObjectId an
  • Aktiver Slug: /api/v1/active/agents/... – löst sich zum persistenten aktiven Mandanten des Benutzers auf

Der aktive Mandant wird während der Anfrageauflösung niemals automatisch aktualisiert. Er kann nur über einen dedizierten API-Endpunkt geändert werden. Health-Endpunkte bleiben außerhalb des Mandanten-Scopes unter /api/v1/health/.

4. UserIdentity-Konstruktion ​

Auth Handler geben eine UserIdentity zurück, die sowohl Benutzer- als auch Mandanteninformationen enthält:

python
return UserIdentity(
    id=user.id,
    name=user.name,
    email=user.email,
    roles=roles,
    acting_within_tenant=tenant,  # kann None sein für Nur-Sysadmin-Anfragen
    is_sys_admin=is_sys_admin,    # abgeleitet von der AIHubSysAdmin Keycloak Realm-Rolle
)

Das is_sys_admin-Flag ist das einzige Signal für den Plattform-Admin-Status – es kurzschließt den Access Checker (siehe "Sysadmin-Zugriff" unten) und ist die Grundlage für die Controller.sys_admin_user()-Abhängigkeit, die Nur-Sysadmin-Endpunkte schützt.

Multi-Tenant-Rollenverwaltung ​

Kernentitäten ​

TenantMetadataEntity

  • Enthält Anzeige-Metadaten (Name, Beschreibung, Zugriffsregeln) für einen Mandanten
  • NICHT die zentrale Quelle der Wahrheit für die Existenz des Mandanten – die Keycloak-Gruppe /tenants/<id> ist maßgeblich. Servicecode muss die Existenz über KeycloakAdminService.tenant_exists() überprüfen, bevor er den Metadaten vertraut.
  • Enthält access_rules, die den Zugriff JEDES Benutzers im Mandanten begrenzen
  • Beispiel: ["aihub.user.agent.>"] gewährt Benutzerebene-Zugriff auf alle Agents

UserTenantRoleEntity

  • Ordnet Benutzern Mandanten mit spezifischen Rollen zu
  • Maßgebliche Quelle für Benutzer-Mandant-Rollen-Beziehungen
  • Benutzer können in verschiedenen Mandanten unterschiedliche Rollen haben

RoleEntity

  • Jede Rolle gehört zu genau einem Mandanten – tenant_id ist erforderlich
  • Der Standardrollensatz (AIHubUser, AIHubAdmin, AIHubAgentUser usw.) wird pro Mandant bei der Erstellung initialisiert
  • Systemweite Rollen existieren nicht mehr; siehe ADR 2026_04_14_tenant_scoped_roles.md

Benutzerprofildaten

  • In Keycloak gespeichert, nicht lokal – KeycloakAdminService.get_user_by_id() / find_user_by_email() zur Abfrage
  • Name, E-Mail und Identitätsattribute fließen alle von Keycloak; die Plattform schreibt nichts in Benutzerdatensätze
  • Rollen sind NICHT dem Benutzerdatensatz zugeordnet – sie werden pro Mandant von der UserTenantRoleEntity abgerufen

Zugreifen auf Benutzerrollen ​

python
# Get user's roles in a specific tenant
roles = user.get_roles(tenant_id)

# Get all access rules for a user in a tenant
access_rules = RoleEntity.get_access_rules_for_roles(roles, tenant_id=tenant_id)

Zugriffskontrolle ​

AccessChecker ​

Die Klasse AccessChecker führt Autorisierungsprüfungen mit zweistufiger Zugriffskontrolle durch:

python
from aihub_lib.auth.access.AccessChecker import AccessChecker

# Erstellen Sie den Checker aus UserIdentity (enthält den Mandantenkontext)
checker = AccessChecker.from_user(user)

# Zugriffsebene prüfen
level = checker.access_level("aihub.user.agent.class-a.id-123")
# Returns: AccessLevel.ACCESS_ADMIN | ACCESS_USER | ACCESS_DENIED

Zweistufige Zugriffsprüfung ​

KRITISCH: Mandantenzugriffsregeln fungieren als OBERGRENZE/GRENZE für Benutzerberechtigungen.

  1. STUFE 1: Bestimmen der Zugriffsebene des Mandanten (Admin oder Benutzer)
  2. STUFE 2: Bestimmen der Zugriffsebene des Benutzers (Admin oder Benutzer)
  3. STUFE 3: Zurückgeben des MINIMUMS beider Ebenen

Beispiel:

python
# Mandant hat: aihub.user.agent.> (Benutzerebene-Zugriff auf alle Agents)
# Benutzer hat: aihub.admin.agent.> (Admin-Ebene-Zugriff auf alle Agents)

# Benutzer erhält ACCESS_USER (begrenzt durch Mandantengrenze)
checker.access_level("aihub.user.agent.class-a.id-1")  # → ACCESS_USER

Zugriffsregelformat ​

Zugriffsregeln folgen einer hierarchischen Punktnotation:

aihub.[admin|user].<resource>.<subresource>.<id>

Platzhalter:

  • * – Platzhalter für eine Ebene: aihub.user.agent.* passt zu jedem einzelnen Agent
  • > – Platzhalter für mehrere Ebenen: aihub.user.agent.> passt zu allen Agents und Unterressourcen

Beispiele:

python
"aihub.admin.>"                    # Voller Admin-Zugriff auf alles
"aihub.user.>"                     # Voller Benutzerzugriff auf alles
"aihub.user.agent.>"               # Benutzerzugriff auf alle Agents
"aihub.user.agent.class-a.*"       # Benutzerzugriff auf alle Class-A-Agents
"aihub.user.agent.class-a.id-123"  # Benutzerzugriff auf spezifischen Agent

Komfortmethoden ​

python
# Spezifischen Agenten-Zugriff prüfen
has_access = checker.has_access_to_agent("class-a", "id-123")
access_level = checker.access_level_for_agent("class-a", "id-123")

# Agentenklassen-Zugriff prüfen
has_access = checker.has_access_to_agent_class("class-a")

# Prozess-Zugriff prüfen
has_access = checker.has_access_to_process("workflow", "proc-456")

# Service-Zugriff prüfen
has_access = checker.has_access_to_service("llm-gateway")

Konfiguration ​

Umgebungsvariablen ​

bash
# Startup-Mandantenkonfiguration (initialisiert beim ersten Start; danach ein gewöhnlicher Mandant)
AIHUB_STARTUP_TENANT_NAME="Swiss AI Hub"
AIHUB_STARTUP_TENANT_DESCRIPTION="This tenant was auto-created on startup of the Swiss AI Hub."
AIHUB_STARTUP_TENANT_ACCESS_RULES="aihub.admin.>"

# Rollenzuweisung bei Benutzeranmeldung
AIHUB_USER_SIGNUP_DEFAULT_ROLES="AIHubUser"
AIHUB_USER_SIGNUP_REGULAR_USER_ROLES="AIHubUser"
AIHUB_USER_SIGNUP_FIRST_ADMIN_USER_ROLES="AIHubAdmin,AIHubUser"

# OAuth2-Konfiguration
OAUTH2_ENABLED=true
OAUTH2_JWKS_URL="https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys"
OAUTH2_ISSUER="https://login.microsoftonline.com/{tenant}/v2.0"
OAUTH2_AUDIENCE="api://{app-id}"

Einstellungsklassen ​

python
from swiss_ai_hub.core.infrastructure.api.startup_tenant_settings import StartupTenantSettings
from swiss_ai_hub.core.infrastructure.api.user_signup_settings import UserSignupSettings

# Auf Startup-Mandanteneinstellungen zugreifen
tenant_settings = StartupTenantSettings()
print(tenant_settings.access_rules_list)  # ['aihub.admin.>']

# Auf Benutzeranmeldungseinstellungen zugreifen
signup_settings = UserSignupSettings()
print(signup_settings.first_admin_user_roles_list)  # ['AIHubAdmin', 'AIHubUser']

Best Practices ​

1. Immer Mandantenkontext bereitstellen ​

Erstellen Sie niemals einen AccessChecker ohne Mandantenzugriffsregeln:

python
# ❌ SCHLECHT: Manuelle Konstruktion ohne Mandantenkontext
checker = AccessChecker(user_access_rules, [])

# ✅ GUT: Verwenden Sie die Factory-Methode, die sowohl Benutzer- als auch Mandantenregeln extrahiert
checker = AccessChecker.from_user(user)  # user ist UserIdentity mit Mandantenkontext

2. Mandantenmitgliedschaft überprüfen ​

Überprüfen Sie immer, ob Benutzer Zugriff auf den Mandanten haben, bevor Sie Operationen durchführen:

python
roles = UserTenantRoleEntity.get_roles_for_user_in_tenant(user_id, tenant_id)
if not roles:
    raise HTTPException(403, "Benutzer nicht dem Mandanten zugewiesen")

3. Die minimal erforderliche Zugriffsebene verwenden ​

Gewähren Sie Benutzern die minimal erforderliche Zugriffsebene für ihre Rolle:

python
# ❌ SCHLECHT: Übermäßig permissiv
access_rules = ["aihub.admin.>"]

# ✅ GUT: Auf spezifische Ressourcen beschränkt
access_rules = ["aihub.user.agent.class-a.>"]

4. Mandantenzugriffsregeln als Grenzen ​

Verwenden Sie Mandantenzugriffsregeln, um den Umfang ALLER Benutzer in einem Mandanten zu begrenzen:

python
# Mandant für das Analyseteam – nur Zugriff auf spezifische Agentenklassen
tenant_access_rules = [
    "aihub.user.agent.analytics.*",
    "aihub.user.service.data-pipeline"
]

# Selbst Admin-Benutzer in diesem Mandanten können nicht auf andere Ressourcen zugreifen

Fehlerbehebung ​

Fehler „Benutzer nicht dem Mandanten zugewiesen“ ​

Ursache: Der Benutzer existiert, hat aber keine Rollen im angeforderten Mandanten.

Lösung: Weisen Sie dem Benutzer Rollen im Mandanten zu:

python
UserTenantRoleEntity.create_or_update(
    user_id=user_id,
    tenant_id=tenant_id,
    roles=["AIHubUser"]
)

„Zugriff verweigert“, obwohl der Benutzer Admin-Rollen hat ​

Ursache: Mandantenzugriffsregeln schränken Benutzerberechtigungen ein. (Sysadmins – Benutzer mit der AIHubSysAdmin Keycloak Realm-Rolle – umgehen diese Überprüfung vollständig; wenn das Problem für einen Sysadmin weiterhin besteht, ist der Bypass selbst falsch konfiguriert.)

Lösung: Überprüfen Sie die Mandantenzugriffsregeln:

python
tenant = TenantMetadataEntity.get_metadata_by_tenant_id(tenant_id)
print(tenant.access_rules)  # Prüfen Sie, was der Mandant zulässt

Leere Mandantenzugriffsregeln = Kein Zugriff ​

Wenn ein Mandant keine Zugriffsregeln ([]) hat, wird ALLEN Benutzern in diesem Mandanten der Zugriff auf alles verweigert.

Lösung: Legen Sie angemessene Mandantenzugriffsregeln fest:

python
tenant.access_rules = ["aihub.user.>"]
tenant.save()

Sicherheitsüberlegungen ​

  • Mounten Sie niemals den TestAuthHandler an Produktionseinstiegspunkten – er befindet sich aus diesem Grund unter core.testing; Produktions-app/main.py-Dateien müssen KeycloakAuthHandler oder TokenAuthHandler verwenden.
  • Validieren Sie JWTs korrekt – überprüfen Sie immer Issuer, Audience und Signatur.
  • Verwenden Sie HTTPS – übertragen Sie Tokens niemals über unverschlüsselte Verbindungen.
  • Rotieren Sie API-Tokens regelmäßig – implementieren Sie Token-Ablauf und -Rotation.
  • Auditieren Sie Änderungen der Zugriffskontrolle – protokollieren Sie alle Rollen- und Berechtigungsänderungen.
  • Prinzip der geringsten Privilegien – gewähren Sie minimal erforderlichen Zugriff.
  • Mandantenisolation – Benutzer können nicht auf Ressourcen außerhalb der Grenzen ihres Mandanten zugreifen.

Migration vom vorherigen System ​

Frühere Versionen riefen Rollen aus Azure AD über die Microsoft Graph API ab. Das neue System:

  • ✅ Speichert Rollen lokal in der UserTenantRoleEntity
  • ✅ Keine externen API-Aufrufe während der Authentifizierung
  • ✅ Mandantenspezifische Rollen für Multi-Tenancy
  • ❌ Keine automatische Rollensynchronisation vom Identity Provider
  • ❌ Kein automatisches Abrufen von Profilbildern vom Identity Provider

Details finden Sie unter ADR: Lokale Multi-Tenant-Rollenverwaltung.

Gebaut mit ❤️ in der Schweiz 🇨🇭