Skip to main content

Single Sign-On (SSO)

Open Saasframe's SSO module provides enterprise-grade Single Sign-On with OIDC and SCIM 2.0 support. This page covers architecture, configuration, and operational guidance.

Supported Identity Providers

IdPOIDC LoginSCIM ProvisioningJIT ProvisioningNotes
Microsoft Entra IDYesYesYesFull OIDC + SCIM support. See Entra ID Setup Guide
ZitadelYesYesYesOIDC + SCIM via Actions/native. See Zitadel Setup Guide
Google WorkspaceYesNoYes (recommended)OIDC only, no SCIM push. See Google Workspace Setup Guide

Architecture

Authentication Flow (OIDC)

User -> Login Page -> HRD Check -> IdP Redirect -> IdP Login -> Callback -> Session
  1. Home Realm Discovery (HRD): User enters email, the system checks if the email domain matches an active SSO config
  2. Authorization Request: OIDC Authorization Code + PKCE flow initiated with encrypted state cookie
  3. IdP Authentication: User authenticates at the identity provider
  4. Callback Processing: Authorization code exchanged for tokens, ID token validated
  5. Account Linking: User matched to existing account (by email or SSO subject) or JIT-provisioned
  6. Session Creation: Auth session established, user redirected to the application

User Provisioning

Two provisioning methods are supported, mutually exclusive per SSO config:

JIT (Just-In-Time) Provisioning

  • Users are created automatically on first OIDC login
  • Profile data extracted from ID token claims
  • Best for: Google Workspace, small organizations, simple setups

SCIM 2.0 Provisioning

  • Users are pre-provisioned by the IdP before first login
  • Supports create, update, deactivate, and delete operations
  • Best for: Entra ID, large organizations needing lifecycle management

Mutual Exclusivity

JIT and SCIM cannot be enabled simultaneously on the same SSO config:

  • Enabling JIT blocks SCIM token creation
  • Creating SCIM tokens blocks enabling JIT
  • Switching requires disabling one before enabling the other

Configuration

Admin Setup Steps

  1. Create SSO Config: Settings -> Single Sign-On -> Create New
  2. Enter IdP Credentials: Issuer URL, Client ID, Client Secret
  3. Add Allowed Domains: Email domains that should use this SSO config
  4. Choose Provisioning: Enable JIT or configure SCIM tokens
  5. Test Connection: Verify the IdP discovery endpoint is reachable
  6. Activate: Enable the config for production use

API Endpoints

EndpointMethodDescription
/api/sso/configPOSTCreate SSO config
/api/sso/configGETList SSO configs
/api/sso/config/:idGETGet config by ID
/api/sso/config/:idPUTUpdate config
/api/sso/config/:idDELETEDelete config (must be inactive)
/api/sso/config/:id/activatePOSTActivate/deactivate config
/api/sso/config/:id/domainsPOSTAdd domain
/api/sso/config/:id/domainsDELETERemove domain
/api/sso/config/:id/testPOSTTest IdP connection
/api/sso/hrdPOSTHome Realm Discovery lookup
/api/sso/initiateGETStart SSO login flow
/api/sso/callback/oidcGETOIDC callback
/api/sso/scim/tokensPOSTCreate SCIM token
/api/sso/scim/tokensGETList SCIM tokens
/api/sso/scim/tokens/:idDELETERevoke SCIM token
/api/sso/scim/v2/UsersPOSTSCIM: Create user
/api/sso/scim/v2/UsersGETSCIM: List users
/api/sso/scim/v2/Users/:idGETSCIM: Get user
/api/sso/scim/v2/Users/:idPATCHSCIM: Update user
/api/sso/scim/v2/Users/:idDELETESCIM: Delete user

Security

OIDC Security Controls

ControlImplementation
PKCES256 with 32-byte random code verifier
State ParameterAES-256-GCM encrypted state cookie with HKDF key derivation
Nonce16-byte random nonce validated in ID token
State ComparisonTiming-safe (crypto.timingSafeEqual)
TTL5-minute state cookie lifetime
CSRFSameSite=Lax cookies + encrypted state parameter
Return URLSanitized to prevent open redirects
Outbound RequestsDiscovery and all provider endpoints reject private/reserved or mixed DNS answers, pin the validated address, disable redirects, and enforce time/size limits

Public HTTPS identity providers work without additional configuration. For a private or on-premises provider, an operator must explicitly list every trusted private endpoint origin in SF_SSO_OIDC_PRIVATE_ORIGIN_ALLOWLIST (comma-separated exact HTTPS origins, including non-default ports). Discovered JWKS, token, and user-info origins are validated independently; allowing the issuer origin does not implicitly allow a different private origin.

SCIM Security Controls

ControlImplementation
Token Formatomscim_ prefix + 32 random bytes (hex)
Storagebcrypt-hashed (cost 10), only prefix stored
One-Time DisplayRaw token returned only at creation
Timing AttackDummy bcrypt hash on zero candidates
Tenant IsolationOrganization ID derived from token, not request

Data Protection

  • OIDC client secrets encrypted at rest (AES via TenantDataEncryptionService)
  • SCIM tokens bcrypt-hashed, never retrievable after creation
  • No PII in server logs
  • All admin endpoints require authentication + feature-based RBAC

Provisioning Methods

JIT Provisioning

When JIT is enabled on an SSO config:

  1. User authenticates via OIDC at the IdP
  2. If the user does not exist in Open Saasframe, a new account is created
  3. Profile data (name, email) extracted from ID token claims
  4. User is assigned to the organization associated with the SSO config
  5. On subsequent logins, profile data is updated from the latest ID token
Limitations
  • User lifecycle not managed (no automatic deactivation)
  • No pre-provisioning (user must log in first)
  • Role assignment requires manual configuration or IdP group claims

SCIM 2.0 Provisioning

When SCIM is configured:

  1. IdP pushes user create/update/delete operations to the SCIM endpoint
  2. Users are pre-provisioned before their first login
  3. Profile changes in the IdP are automatically synced
  4. User deactivation in the IdP triggers deactivation + session revocation
  5. On OIDC login, the existing SCIM-provisioned account is linked (no duplicate)

Supported SCIM Operations:

  • POST /Users — Create user
  • GET /Users — List users (with eq filter support)
  • GET /Users/:id — Get user
  • PATCH /Users/:id — Update user (replace operations on displayName, active, name.*, emails)
  • DELETE /Users/:id — Delete user (soft-delete + deactivation)

Role Mapping

SSO configs support IdP group-to-application role mapping:

  1. Configure appRoleMappings on the SSO config (map IdP group names to app role names)
  2. When the IdP sends group claims in the ID token, roles are automatically assigned
  3. If no mappings are configured, role sync is skipped (user retains existing roles)
Google Workspace

Google does not send group claims by default. Role mapping is not available for Google OIDC without additional configuration.

Environment Variables

VariableRequiredDescription
SSO_STATE_SECRETYes (production)32+ byte secret for state cookie encryption
APP_URL / NEXT_PUBLIC_APP_URLRecommendedBase URL for redirect URI construction
SSO_DEV_SEEDNoSet to true to seed demo SSO config in development
SF_SSO_OIDC_PRIVATE_ORIGIN_ALLOWLISTNoComma-separated exact HTTPS origins allowed to resolve to private ranges for trusted on-prem IdPs

Troubleshooting

"State mismatch — possible CSRF attack"

  • State cookie expired (5-minute TTL). User took too long at the IdP.
  • Browser blocking third-party cookies. Ensure SameSite=Lax cookies are allowed.

"No roles could be resolved from IdP groups"

  • Role mappings are configured but the IdP isn't sending matching group claims.
  • Remove role mappings if not needed, or configure the IdP to send group claims.

User created with wrong provisioning method

  • Check if both JIT and SCIM have been toggled. The system enforces mutual exclusivity.
  • Verify the provisioningMethod field on the user's SSO link record.

SCIM requests return 401

  • Token may be revoked. Check token status in the admin UI.
  • Token format: must include Authorization: Bearer omscim_... header.
  • Check that the SSO config is active.

SCIM requests return 403

  • The SSO config associated with the token is inactive. Activate it first.

HRD not detecting SSO for an email domain

  • Verify the domain is added to the SSO config's allowed domains.
  • Verify the SSO config is activated (inactive configs are not returned by HRD).

IdP-Specific Setup Guides