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
WorkspaceSSOIdentityProviderEntityfrom the request context - Resolving the workspace via
authService.findWorkspaceForSignInUp() - Generating a login token through
generateLoginToken()which usesLoginTokenService - 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
SSObilling entitlement, enforced bySSOService.isSSOEnabled()insso.service.ts. - Provider Storage: Identity providers are stored as
WorkspaceSSOIdentityProviderEntityrecords with protocol-specific configurations in theworkspaceSSOIdentityProvidertable. - URL Construction: The system dynamically builds issuer and callback URLs using
SERVER_URLviabuildIssuerURL()andbuildCallbackUrl(). - 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 viagetAuthorizationUrlForSSO.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →