Skip to main content
Security Assertion Markup Language (SAML) 2.0 is an industry-standard protocol for single sign-on. Cal.com supports SAML authentication through integration with identity providers like Okta, Azure AD, Google Workspace, and others.

Overview

SAML authentication provides:
  • Standards-based SSO using SAML 2.0 protocol
  • Integration with enterprise identity providers
  • Secure assertion-based authentication
  • Automatic user provisioning via SAML attributes

Prerequisites

  • Cal.com Enterprise license with SSO enabled
  • Identity Provider supporting SAML 2.0
  • Separate PostgreSQL database for SAML data
  • Admin access to both Cal.com and your IdP

Setup Guide

1. Configure Environment

Add SAML configuration to your .env file:
.env
# Required: Separate database for SAML data
SAML_DATABASE_URL=postgresql://postgres:@localhost:5450/cal-saml

# Required: Email addresses of SAML administrators
SAML_ADMINS=[email protected],[email protected]

# Required: Encryption key for sensitive data
# Generate with: openssl rand -base64 24
CALENDSO_ENCRYPTION_KEY=your_32_byte_encryption_key

# Optional: Client secret for OAuth 2.0 flows
SAML_CLIENT_SECRET_VERIFIER=your_random_secret

# Optional: Enable for hosted Cal.com features
NEXT_PUBLIC_HOSTED_CAL_FEATURES=1
Source: .env.example:46-55

2. Create SAML Database

Create and migrate the SAML database:
# Create database
creatdb cal-saml

# Run migrations
DATABASE_URL="postgresql://postgres:@localhost:5450/cal-saml" \
  yarn workspace @calcom/prisma migrate deploy

3. Retrieve SAML Metadata

Cal.com provides SAML Service Provider metadata: SAML Configuration Values:
  • Entity ID / Audience: https://saml.cal.com
  • Assertion Consumer Service (ACS) URL: https://your-domain.com/api/auth/saml/callback
  • Single Logout URL: https://your-domain.com/api/auth/saml/logout
  • Name ID Format: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
Source: packages/features/ee/sso/lib/saml.ts:10-13

4. Configure Identity Provider

Create a SAML application in your identity provider with the following settings:

Okta Configuration

  1. Navigate to Applications → Create App Integration
  2. Select SAML 2.0
  3. Configure:
    • Single sign-on URL: https://your-domain.com/api/auth/saml/callback
    • Audience URI: https://saml.cal.com
    • Name ID format: EmailAddress
    • Application username: Email
  4. Attribute Statements (optional):
    firstName  → user.firstName
    lastName   → user.lastName
    email      → user.email
    
  5. Download the IdP metadata XML

Azure AD Configuration

  1. Navigate to Enterprise Applications → New Application
  2. Create a non-gallery application
  3. Configure Single sign-on → SAML
  4. Set:
    • Identifier (Entity ID): https://saml.cal.com
    • Reply URL (ACS): https://your-domain.com/api/auth/saml/callback
    • Sign on URL: https://your-domain.com
  5. User Attributes & Claims:
    email       → user.mail
    firstName   → user.givenname
    lastName    → user.surname
    
  6. Download Federation Metadata XML

Google Workspace Configuration

  1. Navigate to Apps → Web and mobile apps → Add custom SAML app
  2. Download IdP metadata
  3. Configure Service Provider Details:
    • ACS URL: https://your-domain.com/api/auth/saml/callback
    • Entity ID: https://saml.cal.com
    • Name ID format: EMAIL
    • Name ID: Basic Information > Primary email
  4. Attribute mapping:
    firstName   → Basic Information > First name
    lastName    → Basic Information > Last name
    email       → Basic Information > Primary email
    

5. Upload IdP Metadata to Cal.com

  1. Log in to Cal.com as a SAML admin
  2. Navigate to Settings → Security → Single Sign-On
  3. Select your organization/team
  4. Upload or paste your IdP metadata XML
  5. Or manually configure:
    • IdP Entity ID: From IdP metadata
    • SSO URL: From IdP metadata
    • Certificate: X.509 certificate from IdP

SAML Constants

Cal.com uses the following SAML constants:
export const samlTenantID = "Cal.com";
export const samlProductID = "Cal.com";
export const samlAudience = "https://saml.cal.com";
export const samlPath = "/api/auth/saml/callback";
export const clientSecretVerifier = process.env.SAML_CLIENT_SECRET_VERIFIER;
Source: packages/features/ee/sso/lib/saml.ts:10-15

Tenant Structure

SAML connections are scoped per team/organization: Tenant Format: team-{teamId}
export const tenantPrefix = "team-";

// Example: Team with ID 123
const tenant = `team-123`;
Source: packages/features/ee/sso/lib/saml.ts:18 This allows:
  • Multiple teams to have different SAML configurations
  • Isolated authentication per team
  • Organization-level SAML that applies to all sub-teams

User Provisioning

SAML authentication can automatically provision users:

First-Time Login

When a user logs in via SAML for the first time:
// 1. Extract email from SAML assertion
const email = samlResponse.email;

// 2. Check for organization matching email domain
const domain = email.split("@")[1];
const organization = await organizationRepository
  .getVerifiedOrganizationByAutoAcceptEmailDomain(domain);

// 3. Create user and connect to organization
if (organization && ORGANIZATIONS_AUTOLINK) {
  await createUsersAndConnectToOrg({
    emailsToCreate: [email],
    identityProvider: IdentityProvider.SAML,
    identityProviderId: email,
    org: organization,
  });
}
Source: packages/features/ee/sso/lib/sso.ts:34-49

Required Conditions

For automatic provisioning:
  1. ORGANIZATIONS_AUTOLINK=1 must be set
  2. Organization must have verified the email domain
  3. User email domain must match organization domain
  4. User must not already belong to another organization

Permission Management

Self-Hosted Deployments

Only users listed in SAML_ADMINS can configure SAML:
const isSAMLAdmin = (email: string) => {
  const samlAdmins = (process.env.SAML_ADMINS || "").split(",");
  for (const admin of samlAdmins) {
    if (
      admin.toLowerCase() === email.toLowerCase() &&
      admin.toUpperCase() === email.toUpperCase()
    ) {
      return true;
    }
  }
  return false;
};
Source: packages/features/ee/sso/lib/saml.ts:22-30

Hosted Deployments

SAML configuration requires organization-level permissions:
const permissionCheckService = new PermissionCheckService();
const hasPermission = await permissionCheckService.checkPermission({
  userId,
  teamId,
  permission: "organization.read",
  fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
Source: packages/features/ee/sso/lib/saml.ts:51-64

Testing SAML Configuration

1. Verify SAML Connection

Test that SAML metadata is correctly configured:
# Check connection exists
curl -X GET "https://your-domain.com/api/auth/saml/config?tenant=team-123"

2. Initiate Test Login

  1. Open incognito browser window
  2. Navigate to /auth/login
  3. Enter email address that matches SAML domain
  4. Verify redirect to IdP
  5. Log in with IdP credentials
  6. Verify successful redirect back to Cal.com
  7. Confirm user session is created

3. Check SAML Response

Enable debug logging to inspect SAML assertions:
.env
# Add logging for SAML events
DEBUG=saml*

Troubleshooting

Invalid SAML Response

Symptoms: Login fails after IdP redirect Solutions:
  1. Verify ACS URL exactly matches: https://your-domain.com/api/auth/saml/callback
  2. Check Entity ID matches: https://saml.cal.com
  3. Ensure certificate is not expired
  4. Verify clock sync between IdP and Cal.com servers

User Not Found

Error: “Could not find a SSO Identity Provider for your email” Solutions:
  1. Verify SAML connection is configured for the user’s team
  2. Check user has membership in a team with SAML enabled
  3. Confirm email in SAML assertion matches user’s email
  4. Review SAML admin permissions
Source: packages/features/ee/sso/lib/sso.ts:72-77

Auto-Provisioning Not Working

Solutions:
  1. Set ORGANIZATIONS_AUTOLINK=1 in environment
  2. Verify organization has verified the email domain
  3. Check user email domain matches organization’s domain
  4. Ensure user doesn’t already belong to another organization
Source: packages/features/ee/sso/lib/sso.ts:32-54

Permission Denied

Error: “dont_have_permission” Solutions:
  • Self-hosted: Add email to SAML_ADMINS environment variable
  • Hosted: Assign Owner or Admin role in organization
Source: packages/features/ee/sso/lib/saml.ts:46-74

Database Connection Error

Error: Cannot connect to SAML database Solutions:
  1. Verify SAML_DATABASE_URL is correctly set
  2. Ensure database exists and is accessible
  3. Run migrations: DATABASE_URL=$SAML_DATABASE_URL yarn prisma migrate deploy
  4. Check database credentials and network access

SAML Attributes

Cal.com supports the following SAML attributes:
AttributeDescriptionRequired
emailUser’s email addressYes
firstNameUser’s first nameNo
lastNameUser’s last nameNo
usernameUser’s usernameNo
Attributes are case-insensitive and can be mapped in your IdP configuration.

Security Best Practices

  1. Certificate Validation: Always validate SAML assertions with IdP certificate
  2. Encryption: Use CALENDSO_ENCRYPTION_KEY to encrypt sensitive SAML data
  3. HTTPS Only: Never use SAML over HTTP in production
  4. Short Session Lifetime: Configure IdP to issue short-lived assertions
  5. Audit Logging: Monitor SAML login attempts and failures
  6. Certificate Rotation: Rotate SAML certificates before expiration
  7. Restrict Admins: Limit SAML_ADMINS to necessary personnel only

Advanced Configuration

Custom SAML Attributes

Map additional SAML attributes to user fields:
// Custom attribute mapping in IdP
Attributes:
  departmentuser.department
  roleuser.role
  phoneuser.phone
These can be used for:
  • Custom routing logic
  • Team assignment rules
  • User metadata

Multiple SAML Connections

Organizations can configure multiple SAML connections:
// Team A uses Okta
Tenant: team-123
IdP: Okta

// Team B uses Azure AD
Tenant: team-456
IdP: Azure AD
Users are routed to the correct IdP based on their team membership. Source: packages/features/ee/sso/lib/sso.ts:56-70

API Reference

SAML Endpoints

EndpointMethodPurpose
/api/auth/saml/callbackPOSTSAML assertion consumer service
/api/auth/saml/logoutPOSTSingle logout service
/api/auth/saml/configGETRetrieve SAML configuration
/api/auth/saml/metadataGETService provider metadata XML

Connection Types

export type SSOConnection = (SAMLSSORecord | OIDCSSORecord) & {
  type: string;
  acsUrl: string | null;
  entityId: string | null;
  callbackUrl: string | null;
};
Source: packages/features/ee/sso/lib/saml.ts:83-88

Build docs developers (and LLMs) love