.do
Enterprises

Enterprise Single Sign-On (SSO)

Enable enterprise-grade SSO authentication with WorkOS integration supporting SAML, OAuth 2.0, and OpenID Connect

Enterprise Single Sign-On (SSO)

The .do platform provides enterprise-grade Single Sign-On (SSO) capabilities through WorkOS AuthKit, allowing your customers' employees to authenticate using their organization's identity provider.

Overview

Enterprise SSO enables:

  • Centralized Identity Management - Users authenticate through their company's IdP (Okta, Microsoft Entra, Google Workspace, etc.)
  • Streamlined Onboarding - New employees automatically get access through their organization's provisioning
  • Enhanced Security - Enforce MFA, conditional access, and security policies at the IdP level
  • Reduced Support - IT admins manage access through familiar tools
  • Compliance - Meet SOC 2, HIPAA, and enterprise security requirements

Supported Identity Providers

WorkOS supports 50+ identity providers including:

SAML 2.0 Providers

  • Microsoft Entra ID (formerly Azure AD)
  • Okta
  • OneLogin
  • Ping Identity
  • JumpCloud
  • Google Workspace
  • Auth0
  • Duo
  • VMware Workspace ONE
  • Rippling
  • Generic SAML 2.0

OAuth 2.0 / OpenID Connect

  • Google OAuth
  • Microsoft OAuth
  • GitHub OAuth
  • GitLab OAuth

Architecture

sequenceDiagram participant User participant App as Your App participant WorkOS participant IdP as Customer IdP<br/>(Okta, Entra, etc.) User->>App: Click "Sign in with SSO" App->>WorkOS: Redirect to WorkOS SSO WorkOS->>IdP: SAML/OAuth request IdP->>User: Show login page User->>IdP: Enter credentials + MFA IdP->>WorkOS: SAML assertion / OAuth token WorkOS->>WorkOS: Validate & normalize WorkOS->>App: Redirect with auth code App->>WorkOS: Exchange code for profile WorkOS->>App: User profile + organization App->>User: Authenticated session

How It Works

1. Connection Setup

Each customer organization gets a unique SSO Connection:

import { WorkOS } from '@workos-inc/node'

const workos = new WorkOS(process.env.WORKOS_API_KEY)

// Customer's IT admin configures their IdP
const connection = await workos.sso.getConnection('conn_01HXYZ...')

console.log(connection)
// {
//   id: 'conn_01HXYZ...',
//   organizationId: 'org_01ABC...',
//   name: 'Acme Corp Okta',
//   type: 'OktaSAML',
//   state: 'active',
//   domains: [
//     { domain: 'acme.com' }
//   ]
// }

2. User Authentication Flow

When a user signs in with SSO:

// Step 1: Initiate SSO
const authorizationUrl = workos.sso.getAuthorizationURL({
  organization: 'org_01ABC...', // or domain: 'acme.com'
  clientId: process.env.WORKOS_CLIENT_ID,
  redirectUri: 'https://yourapp.do/auth/callback',
  state: 'optional-state-value'
})

// Redirect user to authorizationUrl

// Step 2: Handle callback
const { profile } = await workos.sso.getProfileAndToken({
  code: 'authorization_code',
  clientId: process.env.WORKOS_CLIENT_ID
})

console.log(profile)
// {
//   id: 'prof_01XYZ...',
//   organizationId: 'org_01ABC...',
//   connectionId: 'conn_01HXYZ...',
//   connectionType: 'OktaSAML',
//   email: '[email protected]',
//   firstName: 'Jane',
//   lastName: 'Doe',
//   idpId: 'okta-user-id',
//   rawAttributes: { ... }
// }

3. Domain-Based Routing

Automatically route users to their organization's SSO:

// User enters email: [email protected]
const email = '[email protected]'
const domain = email.split('@')[1]

// Find organization by domain
const organizations = await workos.organizations.listOrganizations({
  domains: [domain]
})

if (organizations.data.length > 0) {
  const org = organizations.data[0]
  // Redirect to SSO for this organization
  const authUrl = workos.sso.getAuthorizationURL({
    organization: org.id,
    clientId: process.env.WORKOS_CLIENT_ID,
    redirectUri: 'https://yourapp.do/auth/callback'
  })
  // Redirect user to authUrl
} else {
  // Fall back to password authentication
}

Admin Portal

WorkOS provides a self-service Admin Portal where your customers' IT admins can configure SSO without developer involvement.

// Generate Admin Portal link for customer
const { link } = await workos.portal.generateLink({
  organization: 'org_01ABC...',
  intent: 'sso', // or 'dsync' for Directory Sync
  returnUrl: 'https://platform.do/settings'
})

// Send link to customer's IT admin
console.log(link)
// https://id.workos.com/portal/launch?secret=secret_01XYZ...

Portal Features

The Admin Portal allows IT admins to:

  1. Configure SSO Connection

    • Select IdP type (Okta, Entra, etc.)
    • Upload SAML metadata or enter OAuth credentials
    • Test connection before going live
  2. Manage Domains

    • Add verified domains (e.g., acme.com)
    • Enable domain-based routing
  3. Monitor Activity

    • View recent authentication events
    • Troubleshoot connection issues
  4. Manage Settings

    • Enable/disable connection
    • Rotate credentials
    • Configure JIT provisioning

Just-In-Time (JIT) User Provisioning

Automatically create user accounts on first SSO login:

on($.User.sso.authenticated, async ({ profile }) => {
  // Check if user exists
  let user = await db.find($.User, { email: profile.email })

  if (!user) {
    // Create user on first login (JIT provisioning)
    user = await $.User.create({
      email: profile.email,
      firstName: profile.firstName,
      lastName: profile.lastName,
      organizationId: profile.organizationId,
      idpId: profile.idpId,
      authMethod: 'sso'
    })

    // Assign default role
    await $.User.assign.Role({
      userId: user.id,
      roleId: 'default-employee-role'
    })

    await send($.Email.send, {
      to: user.email,
      template: 'welcome-sso',
      data: { user }
    })
  }

  // Create session
  return { userId: user.id }
})

Multi-Factor Authentication (MFA)

SSO inherits MFA from the customer's IdP:

  • Customer-Controlled - IT admins configure MFA policies in their IdP
  • Transparent - Your app doesn't handle MFA logic
  • Compliance - Meet enterprise security requirements automatically

Common MFA methods supported by IdPs:

  • SMS/Phone verification
  • Authenticator apps (Google Authenticator, Duo, etc.)
  • Hardware tokens (YubiKey, etc.)
  • Biometrics (Touch ID, Face ID)
  • Push notifications

Security Best Practices

1. Validate Organization Membership

Always verify users belong to the expected organization:

const { profile } = await workos.sso.getProfileAndToken({ code })

// Verify organization
if (profile.organizationId !== expectedOrgId) {
  throw new Error('User does not belong to this organization')
}

2. Handle Connection State

Check connection status before initiating SSO:

const connection = await workos.sso.getConnection(connectionId)

if (connection.state !== 'active') {
  // Show error: "SSO is not configured for your organization"
  return
}

3. Implement Session Management

Create secure, time-limited sessions after SSO authentication:

const session = await createSession({
  userId: user.id,
  organizationId: profile.organizationId,
  authMethod: 'sso',
  expiresIn: '24h'
})

// Set secure cookie
setCookie('session', session.token, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  maxAge: 86400
})

4. Log Authentication Events

Track all SSO authentication for audit trails:

await send($.Event.log, {
  type: 'auth.sso.success',
  userId: user.id,
  organizationId: profile.organizationId,
  metadata: {
    connectionType: profile.connectionType,
    ipAddress: request.ip,
    userAgent: request.headers['user-agent']
  }
})

Pricing

WorkOS SSO pricing (as of 2025):

TierConnectionsPrice per Connection/Month
1-1515 or fewer$125
16-3016-30$100
31-5031-50$80
51-10051-100$65
101+101+Custom pricing

Note: Automatic volume discounts apply as you grow.

Testing SSO

Development Environment

WorkOS provides a Magic Link SSO for testing:

// Use magic link SSO in development
const authUrl = workos.sso.getAuthorizationURL({
  provider: 'authkit', // Magic link provider
  clientId: process.env.WORKOS_CLIENT_ID,
  redirectUri: 'http://localhost:3000/auth/callback'
})

Test Organizations

Create test organizations with different IdPs:

# Create test organization
curl -X POST https://api.workos.com/organizations \
  -H "Authorization: Bearer $WORKOS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Test Corp",
    "domains": ["testcorp.local"]
  }'

Migration from Other Auth Systems

From Auth0

// Auth0
const auth0 = new AuthenticationClient({
  domain: 'your-domain.auth0.com',
  clientId: '...'
})

// WorkOS equivalent
const workos = new WorkOS(process.env.WORKOS_API_KEY)
const authUrl = workos.sso.getAuthorizationURL({
  organization: 'org_...',
  clientId: process.env.WORKOS_CLIENT_ID,
  redirectUri: 'https://yourapp.do/callback'
})

From Okta

// Okta
const authClient = new OktaAuth({
  issuer: 'https://dev-12345.okta.com',
  clientId: '...'
})

// WorkOS equivalent
const workos = new WorkOS(process.env.WORKOS_API_KEY)
// Customers configure Okta via Admin Portal
// You just handle the standardized WorkOS response

Monitoring & Analytics

Track SSO usage and health:

// Get connection events
const events = await workos.events.listEvents({
  events: ['connection.activated', 'connection.deactivated'],
  organizationId: 'org_01ABC...'
})

// Monitor authentication success/failure rates
const metrics = await db.query(`
  SELECT
    date_trunc('day', timestamp) as day,
    count(*) FILTER (WHERE event_type = 'auth.sso.success') as successes,
    count(*) FILTER (WHERE event_type = 'auth.sso.failure') as failures
  FROM events
  WHERE organization_id = $1
  GROUP BY day
  ORDER BY day DESC
`, [organizationId])

Troubleshooting

Common Issues

"SSO configuration not found"

  • Connection not created or inactive
  • Domain not verified
  • Check Admin Portal configuration

"Invalid SAML response"

  • IdP metadata outdated
  • Certificate expired
  • Clock skew between systems

"User not found after SSO"

  • JIT provisioning not enabled
  • User creation failed
  • Check application logs

Debug Mode

Enable detailed logging for troubleshooting:

const workos = new WorkOS(process.env.WORKOS_API_KEY, {
  https: {
    timeout: 30000
  }
})

// Log all requests
workos.on('request', (req) => {
  console.log('WorkOS Request:', req)
})

workos.on('response', (res) => {
  console.log('WorkOS Response:', res)
})