How Single Sign-On (SSO) Works in Twenty CRM: OIDC and SAML Implementation

Twenty CRM supports enterprise Single Sign-On (SSO) through OpenID Connect (OIDC) and SAML protocols, featuring a billing-entitlement-gated architecture where administrators register identity providers via GraphQL and users authenticate through secure, guard-protected HTTP endpoints that generate login tokens upon successful IdP callback.

Twenty CRM implements enterprise-grade SSO using standard authentication protocols to streamline workspace access for corporate users. The system is built on a modular NestJS architecture with strict feature gating via the SSO billing entitlement in the twentyhq/twenty repository. This technical guide explains how the platform handles identity provider registration, URL generation, and secure callback processing for both OIDC and SAML flows.

Architecture and Feature Gating

Billing Entitlement Enforcement

Every SSO operation begins with an entitlement check. The SSOService.isSSOEnabled() method verifies whether the workspace has the SSO billing entitlement active, throwing an SSOException if the feature is unavailable. This gate appears in packages/twenty-server/src/engine/core-modules/sso/services/sso.service.ts at lines 39-51, ensuring that only eligible workspaces can access SSO functionality.

Identity Provider Storage

Identity providers are persisted as WorkspaceSSOIdentityProviderEntity records in the workspaceSSOIdentityProvider table. The entity stores protocol-specific fields: clientID and clientSecret for OIDC providers, or ssoURL and certificate for SAML configurations. The entity definition resides in packages/twenty-server/src/engine/core-modules/sso/workspace-sso-identity-provider.entity.ts.

The SSO Authentication Flow

Step 1: URL Generation and Flow Initiation

The SSOService constructs two critical URLs for each provider. The issuer URL serves as the entry point for IdP redirects, while the callback URL receives the authentication response. These are generated by buildIssuerURL() and buildCallbackUrl() methods using the server's base SERVER_URL environment variable. Users initiate login by requesting an authorization URL via the getAuthorizationUrlForSSO query, which returns a URL pointing to either /auth/oidc/login/:identityProviderId or /auth/saml/login/:identityProviderId.

Step 2: Guard-Protected Endpoints

The HTTP routes in SSOAuthController are protected by protocol-specific guards. OIDCAuthGuard and SAMLAuthGuard (located in src/engine/core-modules/auth/guards) trigger the underlying Passport strategies (passport-openid-client for OIDC, passport-saml for SAML). These guards validate the request context and redirect the user to the corporate IdP login page.

Step 3: Callback Processing and Token Generation

Upon successful authentication, the IdP redirects to /auth/oidc/callback or /auth/saml/callback/:id. The SSOAuthController.authCallback() method handles these routes by:

  • Looking up the WorkspaceSSOIdentityProviderEntity from the request context
  • Resolving the workspace via authService.findWorkspaceForSignInUp()
  • Generating a login token through generateLoginToken() which uses LoginTokenService
  • Redirecting the browser to the UI with the token appended as /verify?loginToken=...

If the connected-account feature flag is enabled, the system also creates a ConnectedAccount record linking the user to the external IdP via authService.createSSOConnectedAccountIfFeatureFlagIsOn().

Configuring SSO Providers via GraphQL

Administrators manage identity providers through the SSOResolver GraphQL API. The resolver exposes mutations for createOIDCIdentityProvider, createSAMLIdentityProvider, editSSOIdentityProvider, and deleteSSOIdentityProvider, plus the getSSOIdentityProviders query.

Registering an OIDC Provider

To create an OIDC identity provider, administrators call the createOIDCIdentityProvider mutation:

mutation CreateOIDC($input: SetupOIDCSsoInput!) {
  createOIDCIdentityProvider(input: $input) {
    id
    type
    name
    status
    issuer
  }
}
{
  "input": {
    "issuer": "https://login.microsoftonline.com/common/v2.0",
    "clientID": "abc123",
    "clientSecret": "super-secret",
    "name": "Azure AD"
  }
}

The SSOService.createOIDCIdentityProvider() method validates the SSO entitlement, discovers the issuer metadata via openid-client, persists the entity, and returns a sanitized DTO.

Querying Authorization URLs

Client applications retrieve login URLs using the getAuthorizationUrlForSSO query:

// Client side (e.g., React)
const { data } = await apolloClient.query({
  query: GET_SSO_URL,
  variables: { providerId: "1234", search: { redirect: "/dashboard" } },
});

const { authorizationURL } = data.getAuthorizationUrlForSSO;
window.location.href = authorizationURL; // redirects to IdP login

This is implemented in SSOService.getAuthorizationUrlForSSO() at lines 200-228, which internally calls buildIssuerURL().

Implementation Details and Code Structure

Core Service Layer

The SSOService class in sso.service.ts encapsulates all business logic for provider CRUD operations, URL construction, and entitlement verification. Key methods include isSSOEnabled(), buildIssuerURL(), buildCallbackUrl(), and getAuthorizationUrlForSSO().

Controller and Guards

The SSOAuthController in sso-auth.controller.ts handles HTTP transport. It coordinates with OIDCAuthGuard and SAMLAuthGuard to initiate flows, then processes callbacks through authCallback() and generateLoginToken(). The controller ensures secure user creation and workspace association before issuing tokens.

Frontend Integration

Client applications implement SSO by querying the authorization URL and performing a full-page redirect:

import { useQuery, gql } from '@apollo/client';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';

const GET_AUTH_URL = gql`
  query($id: String!) {
    getAuthorizationUrlForSSO(identityProviderId: $id, searchParams: { redirect: "/home" }) {
      authorizationURL
    }
  }
`;

export function SSOLogin() {
  const { id } = useParams(); // identityProviderId from route
  const { data, loading } = useQuery(GET_AUTH_URL, { variables: { id } });

  useEffect(() => {
    if (!loading && data?.getAuthorizationUrlForSSO?.authorizationURL) {
      window.location.href = data.getAuthorizationUrlForSSO.authorizationURL;
    }
  }, [loading, data]);

  return <p>Redirecting to your SSO provider…</p>;
}

Summary

  • Feature Gating: All SSO functionality requires the SSO billing entitlement, enforced by SSOService.isSSOEnabled() in sso.service.ts.
  • Provider Storage: Identity providers are stored as WorkspaceSSOIdentityProviderEntity records with protocol-specific configurations in the workspaceSSOIdentityProvider table.
  • URL Construction: The system dynamically builds issuer and callback URLs using SERVER_URL via buildIssuerURL() and buildCallbackUrl().
  • Authentication Flow: Guard-protected endpoints (/auth/oidc/login/:id, /auth/saml/login/:id) initiate flows, while callback endpoints generate login tokens and redirect users to the application.
  • Management API: Administrators configure providers through GraphQL mutations in SSOResolver, while clients retrieve login URLs via getAuthorizationUrlForSSO.

Frequently Asked Questions

What protocols does Twenty CRM support for SSO?

Twenty CRM supports OpenID Connect (OIDC) and SAML protocols for Single Sign-On. The implementation uses passport-openid-client for OIDC flows and passport-saml for SAML integrations, with dedicated guards (OIDCAuthGuard and SAMLAuthGuard) handling each protocol in src/engine/core-modules/auth/guards.

How is the SSO feature enabled in a Twenty CRM workspace?

SSO is controlled by a billing entitlement. The SSOService.isSSOEnabled() method checks this entitlement before any SSO operation and throws an SSOException if the feature is not active for the workspace. This ensures only eligible workspaces can register identity providers or initiate SSO flows.

What happens after a user authenticates with the corporate IdP?

After successful IdP authentication, the user is redirected to /auth/oidc/callback or /auth/saml/callback/:id. The SSOAuthController.authCallback() method processes this response by looking up the provider configuration, resolving the workspace, creating or reusing the user account, and generating a login token via LoginTokenService. The user is then redirected to the application with the token appended to the URL.

Can administrators configure multiple identity providers for one workspace?

Yes, administrators can register multiple identity providers per workspace. Each provider is stored as a separate WorkspaceSSOIdentityProviderEntity record in the workspaceSSOIdentityProvider table, and the getSSOIdentityProviders GraphQL query returns all configured providers for management or display in login interfaces.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →