SAML SSO

Enterprise single sign-on via SAML 2.0. Supports both a global IdP configuration and per-organization IdP setup with email domain routing. Built on the onelogin/php-saml package.

SSO Modes

The sso.mode setting controls how SSO operates. It can be set to one of three values:

  • disabled — SSO is turned off. All SAML routes return 403 Forbidden.
  • global — A single IdP is configured for the entire application. All users authenticate through the same identity provider. Configuration is stored in AppSettings.
  • per_org — Each organization can configure its own IdP. Users are routed to the correct IdP based on their email domain.

Global SSO

In global mode, a single identity provider is configured from the admin panel. The IdP metadata (Entity ID, SSO URL, SLS URL, X.509 certificate) is stored in the application's AppSettings.

Admin Settings → Authentication → SAML SSO
├── IdP Entity ID
├── IdP SSO URL
├── IdP SLS URL (optional)
├── IdP X.509 Certificate
├── NameID Format
└── Attribute Mapping

Per-Organization SSO

In per-org mode, each organization manages its own SAML configuration.

SamlConfiguration Model

Each organization can have one SamlConfiguration record:

saml_configurations
├── id
├── organization_id
├── idp_entity_id
├── idp_sso_url
├── idp_sls_url (nullable)
├── idp_x509_certificate
├── name_id_format (default: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress)
├── attribute_mapping (JSON)
├── force_sso (boolean, default: false)
├── is_active (boolean)
├── created_at
└── updated_at

Email Domain Routing

The OrgDomain model maps verified email domains to organizations. When a user enters their email on the login page, the system checks the domain to determine which organization's IdP to use.

org_domains
├── id
├── organization_id
├── domain (e.g., "acme.com")
├── verified_at (nullable)
├── verification_token
├── created_at
└── updated_at

Domain Verification

Organizations must verify ownership of their domain before it can be used for SSO routing. Verification is done by adding a TXT record to the domain's DNS:

TXT  _saaskitfy-verify.acme.com  "saaskitfy-verify=abc123def456"

The system checks for the TXT record and sets verified_at on success. Unverified domains cannot be used for SSO routing.

SAML Routes

SP Metadata

GET /api/auth/saml/{orgSlug}/metadata

Returns the Service Provider metadata XML. Provide this URL to your IdP during setup. In global mode, use global as the {orgSlug}.

Initiate Login

GET /api/auth/saml/{orgSlug}/login

Redirects the user to the IdP's SSO URL with a SAML AuthnRequest.

Assertion Consumer Service (ACS)

POST /api/auth/saml/{orgSlug}/acs

Receives the SAML Response from the IdP. Validates the assertion, extracts user attributes, and issues a Sanctum token.

Single Logout Service (SLS)

GET /api/auth/saml/{orgSlug}/sls

Handles IdP-initiated logout. Revokes the user's Sanctum token and redirects to the login page.

Attribute Mapping

SAML attributes from the IdP response are mapped to user fields. The mapping is configurable per IdP:

{
    "email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
    "name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
    "first_name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
    "last_name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
}

If name is not provided but first_name and last_name are, the name is constructed by concatenating them.

NameID Format

The NameID format specifies how the user identifier is sent in the SAML assertion. Configurable per IdP with the following options:

  • urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress (default)
  • urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
  • urn:oasis:names:tc:SAML:2.0:nameid-format:transient
  • urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified

JIT Provisioning

When a user authenticates via SAML for the first time and no account exists with that email:

  • A new user account is created with the attributes from the SAML assertion
  • The email is automatically marked as verified
  • In per-org mode, the user is automatically added to the organization as a member
  • No password is set — the user authenticates exclusively via SSO

Force SSO

When force_sso is enabled on a SAML configuration, users belonging to that organization (or matching a verified domain) must authenticate via SSO. Password login, magic link, and OAuth are blocked for these users. This is enforced at the login endpoint level.

Admin Toggle

SSO mode is controlled from the admin panel:

  • Setting: sso.modedisabled, per_org, or global
  • Switching from per_org to global does not delete per-org configurations — they are simply ignored
  • Switching to disabled blocks all SAML routes but preserves all configuration data