From eba738fdc50623dd401fe49b7338f598e88da173 Mon Sep 17 00:00:00 2001 From: Thomas Reitz Date: Mon, 9 Mar 2026 22:39:55 +0100 Subject: [PATCH] feat: add SSO configuration page in admin section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New admin page at /admin/sso with: - Live SSO status indicator (active/not configured) - Step-by-step Azure App Registration guide - Required values table (Tenant ID, Client ID, Client Secret) - API permissions list (openid, profile, email, User.Read) - Redirect URI with copy button - Environment variables template - Technical reference (endpoints, security info) Accessible via sidebar: Administration → SSO-Konfiguration Co-Authored-By: Claude Opus 4.6 --- packages/frontend/src/admin/AdminSsoPage.tsx | 472 +++++++++++++++++++ packages/frontend/src/shell/App.tsx | 2 + packages/frontend/src/shell/AppLayout.tsx | 8 + 3 files changed, 482 insertions(+) create mode 100644 packages/frontend/src/admin/AdminSsoPage.tsx diff --git a/packages/frontend/src/admin/AdminSsoPage.tsx b/packages/frontend/src/admin/AdminSsoPage.tsx new file mode 100644 index 0000000..5b44776 --- /dev/null +++ b/packages/frontend/src/admin/AdminSsoPage.tsx @@ -0,0 +1,472 @@ +import { useQuery } from '@tanstack/react-query'; +import api from '../api/client'; + +interface SsoStatus { + microsoft: boolean; +} + +const cardStyle: React.CSSProperties = { + background: 'var(--color-bg-card)', + borderRadius: 'var(--radius-md)', + boxShadow: 'var(--shadow-sm)', + border: '1px solid var(--color-border)', + padding: '1.5rem', + marginBottom: '1.5rem', +}; + +const codeBlockStyle: React.CSSProperties = { + background: '#1e293b', + color: '#e2e8f0', + borderRadius: 'var(--radius-sm)', + padding: '1rem 1.25rem', + fontSize: '0.8125rem', + fontFamily: "'JetBrains Mono', 'Fira Code', 'Consolas', monospace", + lineHeight: 1.7, + overflowX: 'auto', + whiteSpace: 'pre', + marginTop: '0.75rem', +}; + +const inlineCodeStyle: React.CSSProperties = { + background: 'var(--color-bg)', + padding: '0.125rem 0.375rem', + borderRadius: 'var(--radius-sm)', + fontSize: '0.8125rem', + fontFamily: "'JetBrains Mono', 'Fira Code', 'Consolas', monospace", + color: '#1e40af', +}; + +const stepStyle: React.CSSProperties = { + display: 'flex', + gap: '1rem', + marginBottom: '1.5rem', +}; + +const stepNumberStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: 32, + height: 32, + borderRadius: '50%', + background: 'var(--color-primary)', + color: 'white', + fontSize: '0.875rem', + fontWeight: 700, + flexShrink: 0, +}; + +const stepContentStyle: React.CSSProperties = { + flex: 1, + paddingTop: '0.25rem', +}; + +const h3Style: React.CSSProperties = { + fontSize: '1rem', + fontWeight: 600, + color: 'var(--color-text)', + marginBottom: '0.5rem', +}; + +const pStyle: React.CSSProperties = { + fontSize: '0.875rem', + color: 'var(--color-text-secondary)', + lineHeight: 1.6, + marginBottom: '0.5rem', +}; + +const tableStyle: React.CSSProperties = { + width: '100%', + borderCollapse: 'collapse', + marginTop: '0.75rem', +}; + +const thStyle: React.CSSProperties = { + padding: '0.625rem 0.75rem', + textAlign: 'left', + fontSize: '0.75rem', + textTransform: 'uppercase', + color: 'var(--color-text-muted)', + borderBottom: '1px solid var(--color-border)', + background: 'var(--color-bg)', +}; + +const tdStyle: React.CSSProperties = { + padding: '0.625rem 0.75rem', + fontSize: '0.8125rem', + borderBottom: '1px solid var(--color-border)', + verticalAlign: 'top', +}; + +export function AdminSsoPage() { + const { data: ssoStatus, isLoading } = useQuery({ + queryKey: ['sso', 'status'], + queryFn: async () => { + const res = await api.get('/auth/sso/status'); + return res.data; + }, + }); + + const redirectUri = `${window.location.origin}/api/v1/auth/sso/microsoft/callback`; + + return ( +
+
+

SSO-Konfiguration

+
+ + {/* Status-Anzeige */} +
+
+ + + + + + +

Microsoft Entra ID (Azure AD)

+
+ +
+ + + {isLoading + ? 'Status wird geladen...' + : ssoStatus?.microsoft + ? 'SSO ist aktiv — Benutzer können sich mit Microsoft anmelden' + : 'SSO ist nicht konfiguriert — folge der Anleitung unten'} + +
+
+ + {/* Einrichtungs-Anleitung */} +
+

+ Einrichtungsanleitung +

+ + {/* Schritt 1 */} +
+
1
+
+

App Registration im Azure Portal anlegen

+

+ Gehe zum{' '} + + Azure Portal → App registrations + + {' '}und klicke auf New registration. +

+ + + + + + + + + + + + + + + + + + + + + +
FeldWert
NameINSIGHT Platform
Supported account types + Accounts in this organizational directory only
+ + (Single tenant — nur euer Azure AD) + +
Redirect URI + Platform: Web
+ URI: {redirectUri} +
+
+
+ + {/* Schritt 2 */} +
+
2
+
+

Client Secret erstellen

+

+ In der App Registration → Certificates & secretsNew client secret. +

+ + + + + + + + + + + + + + + + + +
FeldWert
DescriptionINSIGHT Platform SSO
Expires + 24 months (empfohlen)
+ + Danach muss das Secret erneuert werden + +
+
+ Wichtig: Den Value des Secrets sofort kopieren — er wird nur einmalig angezeigt! +
+
+
+ + {/* Schritt 3 */} +
+
3
+
+

API Permissions konfigurieren

+

+ In der App Registration → API permissionsAdd a permissionMicrosoft GraphDelegated permissions. +

+

Folgende Berechtigungen hinzufügen:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PermissionTypBeschreibung
openidDelegatedSign users in
profileDelegatedView users' basic profile
emailDelegatedView users' email address
User.ReadDelegatedSign in and read user profile
+

+ Dann Grant admin consent klicken, um die Berechtigungen für alle Benutzer freizugeben. +

+
+
+ + {/* Schritt 4 */} +
+
4
+
+

Werte aus Azure Portal kopieren

+

+ In der App Registration → Overview findest du die benötigten Werte: +

+ + + + + + + + + + + + + + + + + + + + + +
Azure Portal FeldUmgebungsvariable
Directory (tenant) IDAZURE_TENANT_ID
Application (client) IDAZURE_CLIENT_ID
Client secret value (aus Schritt 2)AZURE_CLIENT_SECRET
+
+
+ + {/* Schritt 5 */} +
+
5
+
+

Umgebungsvariablen auf dem Server setzen

+

+ Trage die Werte in die .env-Datei auf dem Server ein: +

+
+{`AZURE_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +AZURE_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +AZURE_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +AZURE_REDIRECT_URI=${redirectUri}`} +
+
+
+ + {/* Schritt 6 */} +
+
6
+
+

Core-Service neu starten

+

+ Nach dem Setzen der Umgebungsvariablen den Backend-Service neu starten: +

+
+{`docker compose restart core`} +
+

+ Nach dem Neustart sollte der Status oben auf{' '} + aktiv wechseln und + der „Mit Microsoft anmelden"-Button auf der Login-Seite erscheinen. +

+
+
+
+ + {/* Funktionsweise */} +
+

+ Funktionsweise +

+
+
+

Erstanmeldung

+

+ Wenn ein Benutzer sich zum ersten Mal mit Microsoft anmeldet, + wird automatisch ein Konto angelegt (Rolle: Benutzer). + Existiert bereits ein Konto mit derselben E-Mail-Adresse, + wird das Microsoft-Konto automatisch verknüpft. +

+
+
+

Sicherheit

+

+ Der SSO-Flow nutzt den OAuth2 Authorization Code Flow. + Das Client Secret verlässt nie den Server. + CSRF-Schutz via State-Parameter. + Die Multi-Faktor-Authentifizierung (MFA) von Microsoft wird unterstützt. +

+
+
+
+ + {/* Technische Details */} +
+

+ Technische Referenz +

+ + + + + + + + + + + + + + + + + + + + + +
EndpointBeschreibung
GET /api/v1/auth/sso/microsoftStartet den SSO-Flow (Redirect zu Microsoft)
GET /api/v1/auth/sso/microsoft/callbackCallback von Microsoft (Token-Exchange + User-Provisioning)
GET /api/v1/auth/sso/statusPrüft ob SSO konfiguriert ist (für Login-Seite)
+ +

Redirect URI

+

+ Diese URI muss exakt so in der Azure App Registration eingetragen sein: +

+
+ {redirectUri} + +
+
+
+ ); +} diff --git a/packages/frontend/src/shell/App.tsx b/packages/frontend/src/shell/App.tsx index 31959c5..32468f8 100644 --- a/packages/frontend/src/shell/App.tsx +++ b/packages/frontend/src/shell/App.tsx @@ -6,6 +6,7 @@ import { AppLayout } from './AppLayout'; import { DashboardPage } from './DashboardPage'; import { AdminUsersPage } from '../admin/AdminUsersPage'; import { AdminTenantsPage } from '../admin/AdminTenantsPage'; +import { AdminSsoPage } from '../admin/AdminSsoPage'; import { ProfilePage } from '../profile/ProfilePage'; function PrivateRoute({ children }: { children: React.ReactNode }) { @@ -46,6 +47,7 @@ export function App() { } /> } /> } /> + } /> {/* Fallback */} diff --git a/packages/frontend/src/shell/AppLayout.tsx b/packages/frontend/src/shell/AppLayout.tsx index 810a548..711d0d1 100644 --- a/packages/frontend/src/shell/AppLayout.tsx +++ b/packages/frontend/src/shell/AppLayout.tsx @@ -51,6 +51,14 @@ export function AppLayout() { > Mandanten + + `${styles.navLink} ${isActive ? styles.active : ''}` + } + > + SSO-Konfiguration + )}