Enterprise SSO und SCIM fur B2B SaaS: Eine pragmatische Implementierung ohne WorkOS
Das Gespräch folgt fast immer demselben Drehbuch. Dein Enterprise-Interessent liebt die Demo, der Champion hat Budget, der Deal ist nah dran - dann fragt jemand aus der IT-Sicherheit: "Unterstutzt ihr SSO?" Eine Woche später schreibt derselbe Kontakt: "Wir brauchen auch SCIM-Provisioning, bevor wir zur Rechtsabteilung gehen können."
Fur die meisten B2B-SaaS-Grunder fuhrt dieser Moment direkt zur WorkOS-Preisseite. Bei 2-4 Euro pro Seat pro Monat sehen die Zahlen mit 50 Nutzern gut aus. Bei 2.000 Seats zahlst du 48.000-96.000 Euro pro Jahr fur eine Middleware-Schicht zwischen deiner App und dem Identity-Provider deines Kunden. Diese Gebuhren entstehen jedes Jahr und gehen von deinem ACV ab, den du hart erarbeitet hast.
Die gute Nachricht: Der 80%-Fall - SAML-2.0-SSO mit Okta, Microsoft Entra ID (ehemals Azure AD) und Google Workspace, plus grundlegendes SCIM-2.0-Gruppen- und Benutzer-Sync - ist in zwei bis vier Wochen fokussierter Engineering-Arbeit selbst umsetzbar. Dieser Beitrag zeigt, wie wir das in Symfony- und Next.js-Stacks angehen, was die echten Edge-Cases sind und die zwei spezifischen Szenarien, in denen eine verwaltete Plattform das Geld tatsächlich wert ist.
Warum Enterprise-Käufer Enterprise-SSO fordern
Bevor wir zur Implementierung kommen, ist es hilfreich zu verstehen, was IT-Sicherheitsteams tatsächlich schutzen, wenn sie SSO verlangen. Sie wollen eine einzige maßgebliche Wahrheitsquelle fur Benutzerzugang - ihren Identity Provider (IdP). Wenn ein Mitarbeiter das Unternehmen verlässt, widerruft die IT den Zugang in Okta oder Entra ID, und dieser Widerruf kaskadiert sofort auf jede verbundene Anwendung. Ohne SSO bedeutet Deprovisioning, sich bei jedem SaaS-Tool einzeln anzumelden, was zu monatelang lingerndem Zugang nach dem Ausscheiden fuhrt.
SCIM (System for Cross-domain Identity Management) erweitert diese Kontrolle auf das Provisioning. Statt dass Mitarbeiter Konten in deiner App selbst registrieren, pusht der IdP Benutzer- und Gruppendaten per standardisierter REST-API zu dir. Die IT definiert, wer Zugang zu was bekommt - deine App konsumiert nur die Anweisungen.
Das ist kein Nice-to-have fur Enterprise-Kunden. Es ist eine harte Anforderung in Beschaffungs-Checklisten, ISO-27001-Audits und SOC-2-Typ-II-Kontrollen. Wenn du an Unternehmen mit mehr als 500 Mitarbeitern verkaufen möchtest, brauchst du beides.
Was SAML 2.0 tatsächlich beinhaltet
SAML 2.0 ist ein XML-basierter Standard fur föderierte Authentifizierung. Der Flow hat drei Akteure: den Service Provider (deine App), den Identity Provider (Okta, Entra ID, Google Workspace) und den Browser des Benutzers, der signierte XML-Assertions zwischen ihnen hin- und hertransportiert.
Der SP-initiierte Flow, den die meisten Enterprise-Käufer erwarten, funktioniert so:
- Benutzer besucht deine App-Login-Seite und klickt "Mit SSO anmelden".
- Deine App leitet den Benutzer zur SSO-URL des IdP mit einer kodierten AuthnRequest weiter.
- Der IdP authentifiziert den Benutzer (oder nutzt eine bestehende Session) und gibt eine signierte SAML-Response an die Assertion Consumer Service (ACS) URL deiner App zuruck.
- Deine App validiert die Signatur gegen das öffentliche Zertifikat des IdP, extrahiert Benutzerattribute aus der Assertion und erstellt oder aktualisiert die Benutzersession.
Der XML-Unterbau wirkt einschuchternd, wird aber vom nbgrp/onelogin-saml-bundle-Paket in Symfony gut gehandhabt. Du konfigurierst einen SP-Metadata-Eintrag pro Kundenmandant, speicherst das IdP-Metadata-XML (oder rufst es von der Metadata-URL des IdP ab), und das Bundle übernimmt Redirect-Generierung und Response-Validierung.
In einem Multi-Tenant-SaaS ist die kritische Designentscheidung das Tenant-Routing - zu wissen, welche IdP-Konfiguration zu laden ist, bevor du die AuthnRequest ausstellen kannst. Wir routen typischerweise anhand einer Subdomain (acme.deinsaas.com) oder anhand einer E-Mail-Domain, die der Benutzer in ein Pre-SSO-Feld tippt.
SAML in Symfony einrichten
Ein praktisches Symfony-Setup erfordert vier Dinge: die Bundle-Konfiguration, eine SamlConnection-Entity pro Mandant, ein Service, der die korrekte Verbindung aus der eingehenden Anfrage auflöst, und einen Controller, der den ACS-Callback behandelt.
// src/Entity/SamlConnection.php (vereinfacht)
#[ORM\Entity]
class SamlConnection
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
private int $id;
#[ORM\Column(unique: true)]
private string $emailDomain; // z.B. "acme.com"
#[ORM\Column(type: 'text')]
private string $idpMetadataXml;
#[ORM\Column]
private string $entityId; // Deine SP-Entity-ID fur diesen Mandanten
}
Das Bundle routet den ACS-Callback an einen einzelnen Endpunkt, sodass dein Controller den Mandanten aus der SAML-InResponseTo-ID oder einem Relay-State-Parameter suchen muss, den du in die AuthnRequest einbettest. Speichere den Relay State als kurzlebigen Cache-Eintrag (Redis funktioniert gut), der eine zufällige ID auf den Mandantenidentifier abbildet.
Ein Detail, das Teams oft uberrascht: Okta und Entra ID senden NameID standardmäßig im E-Mail-Format, aber Google Workspace sendet einen Google-internen persistenten Identifier. Bilde NameID nur dann auf deine interne Benutzer-ID ab, wenn du sicher bist, dass sie uber IdP-Rekonfigurationen hinweg stabil bleibt. E-Mail ist portibler, aber Benutzer können ihre E-Mail ändern. Eine dedizierte external_idp_id-Spalte pro Benutzer, getrennt von ihrer E-Mail, ist das sicherere Modell.
SCIM 2.0: Die Provisioning-Seite
SCIM 2.0 ist eine REST-API, die deine App exponiert. Der IdP ruft sie auf, um Benutzer und Gruppen zu pushen. Die Kernendpunkte, die du implementieren musst, sind:
GET /scim/v2/Users- Benutzer auflisten, mit Filter-UnterstutrzungGET /scim/v2/Users/{id}- Einzelnen Benutzer abrufenPOST /scim/v2/Users- Benutzer erstellenPUT /scim/v2/Users/{id}- Benutzereintrag ersetzenPATCH /scim/v2/Users/{id}- Partielle Aktualisierung (Deaktivierung verwendet typischerweise dies)DELETE /scim/v2/Users/{id}- DeprovisionierenGET /scim/v2/Groups,POST,PUT,PATCH,DELETE- gleiche Form fur Gruppen
Die SCIM-Spec (RFC 7644) schreibt spezifisches JSON-Schema fur Anfragen und Antworten vor. Okta und Entra ID validieren dies strikt. Der häufigste Fehler bei ersten Tests sind nicht-konforme Listen-Responses - der ListResponse-Envelope mit totalResults, startIndex, itemsPerPage und Resources ist auch bei Einzeltreffer-Abfragen erforderlich.
Die Authentifizierung zwischen dem IdP und deinem SCIM-Endpunkt erfolgt mit einem Bearer-Token, das du pro Kundenmandant generierst und in der IdP-Admin-Konsole einfugst. Speichere es gehasht (bcrypt oder Argon2, nicht SHA-256) neben der SamlConnection-Entity.
Implementiere SCIM als Symfony-API-Platform-Ressource oder als einfachen Controller - beides funktioniert. API Platform gibt dir Filter- und Paginierungsunterstutrzung fast kostenlos, was den filter-Parameter abdeckt, den Okta bei der initialen Synchronisierung intensiv verwendet.
Just-in-Time-Provisioning vs. vollständiges SCIM
Hier sparen Teams Zeit: JIT-Provisioning genugt oft fur das erste Jahr. Statt die vollständige SCIM-API zu implementieren, erstellst oder aktualisierst du einen Benutzereintrag zur SAML-Assertion-Zeit, mit den Attributen, die der IdP in der Assertion sendet (E-Mail, Name, Abteilung, Gruppen).
JIT ist einfacher und eliminiert das IdP-Polling-Problem, hat aber eine echte Einschränkung: Deprovisioning erfolgt nicht sofort. Ein entlassener Mitarbeiter kann sich noch anmelden, bis sein IdP-Konto deaktiviert wird - aber wenn deine App die SAML-Assertion bei jeder Session-Erneuerung pruft, ist dieses Fenster höchstens so lang wie deine Session-Lifetime. Konfiguriere kurze Sessions (4-8 Stunden) und verlange Re-Authentifizierung, und das Risiko ist fur die meisten Enterprise-Käufer handhabbar.
Vollständiges SCIM wird notwendig, wenn:
- Der Kunde sofortiges Deprovisioning auf IdP-Ebene verlangt (Finanzdienstleistungen, Gesundheitswesen, Behörden)
- Du Gruppen-Mitgliedschaften synchronisieren musst, die die rollenbasierte Zugriffskontrolle steuern, bevor sich ein Benutzer zum ersten Mal anmeldet
- Das IT-Team des Kunden den Benutzerzugang ausschließlich vom IdP aus verwalten möchte
Fur SaaS, das auf Mid-Market-Enterprise zielt (200-2.000 Seat-Deals), ist JIT zuerst, SCIM später eine vertretbare Reihenfolge.
Verschachtelte Gruppen-Synchronisation: Der Edge Case, der alle stolpert
Standard-SCIM behandelt Gruppen als flach. Okta pusht Gruppen-Mitgliedschaft als Liste von Benutzer-IDs innerhalb einer Group-Ressource. Microsoft Entra ID unterstutzt hingegen verschachtelte Gruppen (eine Gruppe kann andere Gruppen enthalten) und pusht standardmäßig nur die direkten Mitglieder jeder Gruppe - was bedeutet, dass deine App kein Signal uber die transitiven Mitgliedschaften erhält, die das IT-Team deines Kunden sorgfältig aufgebaut hat.
Drei Ansätze zur Handhabung:
Option 1: Beim Sync abflachen. Wenn du ein Group-PATCH von Entra ID erhältst, ruf die Microsoft Graph API auf, um transitive Mitglieder aufzulösen. Das erfordert ein zusätzliches OAuth-2.0-Client-Credentials-Token mit GroupMember.Read.All. Komplex, aber vollständig.
Option 2: Verschachtelte Gruppen ignorieren und die Einschränkung dokumentieren. Teile dem IT-Team des Kunden mit, dass es nur flache Gruppen an deine Anwendung pushen soll. Die meisten Enterprise-IT-Teams kennen diese Einschränkung bei Drittanbieter-SaaS.
Option 3: Rollen-Mapping an die Assertion delegieren. Verwende SAML-Attribut-Statements (ein groups-Attribut, das der IdP aus der Gruppen-Mitgliedschaft fullt) fur die Zugriffskontrolle statt SCIM-Gruppen-Sync. Das IT-Team verwaltet die Gruppenmitgliedschaft im IdP; deine App bildet den Gruppennamen auf einen Berechtigungssatz ab.
Option 3 ist der pragmatischste Ansatz fur den 80%-Fall. Option 1 ist die "korrekte" Antwort fur Enterprise-Käufer mit komplexen Organisationsstrukturen, die verschachtelte Gruppen als harte Anforderung definiert haben.
Wann WorkOS tatsächlich sinnvoll ist
Das alles soll nicht heißen, dass WorkOS nie die richtige Wahl ist. Zwei Situationen rechtfertigen die Plattformgebuhren:
Schnelligkeit bis zum Umsatz. Wenn ein Enterprise-Deal in drei Wochen abschließt und du keine SSO-Infrastruktur hast, bringt dich WorkOS in zwei Tagen unblocked. Bei 2-4 Euro pro Seat bei einem 100-Seat-Erstdeal sind das 200-400 Euro/Monat - weit weniger als die Engineering-Kosten einer uberstuten Implementierung. Baue es in der nächsten Sprint-Runde sauber selbst und migriere den Mandanten weg.
Directory-Sync mit komplexem Attribut-Mapping. Wenn dein Produkt-Zugriffsmodell von feingranularen Attributen abhängt, die aus Active Directory synchronisiert werden - Berufsbezeichnungen, Kostenstellen, Manager-Hierarchien - und dein Kundenstamm große Unternehmen mit nicht-standardmäßigen AD-Schemas umfasst, spart die Normalisierungsschicht von WorkOS tatsächlich Wochen. Die 4-Euro/Seat/Monat-Kosten werden vertretbar, wenn sie einmalige IT-Integrationsarbeit fur jeden neuen Kunden ersetzen.
Außerhalb dieser zwei Szenarien ist die eigene Implementierung die bessere langfristige Investition. Du besitzt das Datenmodell, kontrollierst die Compliance-Geschichte und zahlst keine Steuer, die mit deinem ARR wächst.
Das Next.js-Frontend anschließen
Der SSO-Flow ist weitgehend serverseitig, aber dein Next.js-Frontend muss den Pre-SSO-E-Mail-Eingabeschritt behandeln und geeignete Fehlerstatus anzeigen, wenn die Assertion-Validierung fehlschlägt.
Wir implementieren typischerweise eine /auth/sso-Route, die ein minimales Formular zur Eingabe der Arbeits-E-Mail des Benutzers rendert. Beim Absenden ruft eine Next.js-API-Route das Symfony-Backend auf, sucht die SAML-Verbindung fur die E-Mail-Domain und gibt die IdP-Redirect-URL zuruck. Das Frontend fuhrt einen clientseitigen Redirect zu dieser URL mit der kodierten AuthnRequest durch. Dadurch bleibt die SAML-Logik vollständig in Symfony, während die Next.js-App eine saubere Integrationsoberfläche erhält.
Fehlerbehandlung verdient Aufmerksamkeit. SAML-Fehler erscheinen als undurchsichtige Fehlercodes in der Assertion oder als HTTP-Fehlerantworten vom IdP. Ordne sie benutzerfreundlichen Meldungen zu: "Dein Konto ist dieser Anwendung nicht zugewiesen", "Deine Session ist abgelaufen, bitte melde dich erneut an" und so weiter. Ein verwirrender SSO-Fehler um 8 Uhr morgens, wenn ein Direktor dein Produkt einem Interessenten vorführt, ist ein Support-Ticket, das du nicht haben willst.
Praktische nächste Schritte
Wenn das auf deiner Roadmap steht, gliedert sich die Arbeit in vier sequentielle Teile: SAML-Verbindungsverwaltungs-UI (damit dein Team oder Kunden ihren IdP konfigurieren können), den SP-initiieren SAML-Flow mit JIT-Provisioning, den SCIM-Endpunkt fur Organisationen, die ihn benötigen, und schließlich verschachtelte Gruppen-Behandlung, wenn ein spezifischer Kunde sie verlangt.
Ein fokussierter Backend-Engineer kann die ersten zwei Teile in zwei Wochen produktionsbereit haben. SCIM fugt eine weitere Woche hinzu. Verschachtelte Gruppen-Synchronisation ist ein separates Projekt, das auf den spezifischen IdP und Anwendungsfall zugeschnitten ist.
Wenn dein Team ausgelastet ist oder du dich auf einen Enterprise-Deal vorbereitest und das sauber umgesetzt haben möchtest, schauen wir uns das gerne an. Schreib uns unter hello@wolf-tech.io oder besuche wolf-tech.io fur einen kurzen Anruf. Wir haben diesen Stack fur mehrere B2B-SaaS-Produkte gebaut und können den Aufwand nach einem 30-minutigen Gespräch genau einschätzen.
Fur Teams, die bereits ein MVP ausgeliefert haben und nun mit Enterprise-Beschaffungsanforderungen konfrontiert sind, decken unsere Code-Quality-Consulting- und Custom-Software-Development-Dienste genau diese Art von kritischer Infrastruktur ab - einschließlich der Sicherheitsprufung, die Enterprise-Käufer verlangen werden, sobald SSO vorhanden ist.

