Enterprise SSO & SCIM: SSO and SCIM provisioning for B2B enterprise customers with directory synchronization
---
# DOCUMENT BOUNDARY
---
# Add Modular SCIM provisioning
> Automate user provisioning with SCIM. Directory API and webhooks for real-time user data sync
This guide shows you how to automate user provisioning with SCIM using Scalekit’s Directory API and webhooks. You’ll learn to sync user data in real-time, create webhook endpoints for instant updates, and build automated provisioning workflows that keep your application’s user data synchronized with your customers’ directory providers. With [SCIM Provisioning](/directory/guides/user-provisioning-basics) from Scalekit, you can: * Use **webhooks** to listen for events from your customers’ directory providers (e.g., user updates, group changes) * Use **REST APIs** to list users, groups, and directories on demand Scalekit abstracts the complexities of various directory providers, giving you a single interface to automate user lifecycle management. This enables you to create accounts for new hires during onboarding, deactivate accounts when employees depart, and adjust access levels as employees change roles.  ### Build with a coding agent * Claude Code ```bash /plugin marketplace add scalekit-inc/claude-code-authstack ``` ```bash /plugin install modular-scim@scalekit-auth-stack ``` * Codex ```bash curl -fsSL https://raw.githubusercontent.com/scalekit-inc/codex-authstack/main/install.sh | bash ``` ```bash # Restart Codex # Plugin Directory -> Scalekit Auth Stack -> install modular-scim ``` * GitHub Copilot CLI ```bash copilot plugin marketplace add scalekit-inc/github-copilot-authstack ``` ```bash copilot plugin install modular-scim@scalekit-auth-stack ``` * 40+ agents ```bash npx skills add scalekit-inc/skills --skill implementing-scim-provisioning ``` [Continue building with AI →](/dev-kit/build-with-ai/scim/) ## User provisioning with Scalekit’s directory API [Section titled “User provisioning with Scalekit’s directory API”](#user-provisioning-with-scalekits-directory-api) Scalekit’s directory API allows you to fetch information about users, groups, and directories associated with an organization on-demand. This approach is ideal for scheduled synchronization tasks, bulk data imports, or when you need to ensure your application’s user data matches the latest directory provider state. Let’s explore how to use the Directory API to retrieve user and group data programmatically. 1. ### Setting up the SDK [Section titled “Setting up the SDK”](#setting-up-the-sdk) Before you begin, ensure that your organization [has a directory set up in Scalekit](/guides/user-management/scim-provisioning/). Scalekit offers language-specific SDKs for fast SSO integration. Use the installation instructions below for your technology stack: * Node.js ```bash npm install @scalekit-sdk/node ``` * Python ```sh pip install scalekit-sdk-python ``` * Go ```sh go get -u github.com/scalekit-inc/scalekit-sdk-go ``` * Java ```groovy /* Gradle users - add the following to your dependencies in build file */ implementation "com.scalekit:scalekit-sdk-java:2.0.11" ``` ```xml com.scalekit scalekit-sdk-java 2.0.11 ``` Navigate to **Dashboard > Developers > Settings > API Credentials** to obtain your credentials. Store your credentials securely in environment variables: .env ```shell 1 # Get these values from Dashboard > Developers > Settings > API Credentials 2 SCALEKIT_ENVIRONMENT_URL='https://b2b-app-dev.scalekit.com' 3 SCALEKIT_CLIENT_ID='' 4 SCALEKIT_CLIENT_SECRET='' ``` 2. ### Initialize the SDK and make your first API call [Section titled “Initialize the SDK and make your first API call”](#initialize-the-sdk-and-make-your-first-api-call) Initialize the Scalekit client with your environment variables and make your first API call to list organizations. * cURL Terminal ```bash 1 # Security: Replace with a valid access token from Scalekit 2 # This token authorizes your API requests to access organization data 3 4 # Use case: Verify API connectivity and test authentication 5 # Examples: Initial setup testing, debugging integration issues 6 7 curl -L "https://$SCALEKIT_ENVIRONMENT_URL/api/v1/organizations?page_size=5" \ 8 -H "Authorization: Bearer " ``` * Node.js Node.js ```javascript 1 import { ScalekitClient } from '@scalekit-sdk/node'; 2 3 // Initialize Scalekit client with environment variables 4 // Security: Always use environment variables for sensitive credentials 5 const scalekit = new ScalekitClient( 6 process.env.SCALEKIT_ENVIRONMENT_URL, 7 process.env.SCALEKIT_CLIENT_ID, 8 process.env.SCALEKIT_CLIENT_SECRET, 9 ); 10 11 try { 12 // Use case: Retrieve organizations for bulk user provisioning workflows 13 // Examples: Multi-tenant applications, enterprise customer onboarding 14 const { organizations } = await scalekit.organization.listOrganization({ 15 pageSize: 5, 16 }); 17 18 console.log(`Organization name: ${organizations[0].display_name}`); 19 console.log(`Organization ID: ${organizations[0].id}`); 20 } catch (error) { 21 console.error('Failed to list organizations:', error); 22 // Handle error appropriately for your application 23 } ``` * Python Python ```python 1 from scalekit import ScalekitClient 2 import os 3 4 # Initialize the SDK client with environment variables 5 # Security: Use os.getenv() to securely access credentials 6 scalekit_client = ScalekitClient( 7 env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), 8 client_id=os.getenv("SCALEKIT_CLIENT_ID"), 9 client_secret=os.getenv("SCALEKIT_CLIENT_SECRET") 10 ) 11 12 try: 13 # Use case: Sync user data across multiple organizations 14 # Examples: Scheduled provisioning tasks, HR system integration 15 org_list = scalekit_client.organization.list_organizations(page_size=100) 16 17 if org_list: 18 print(f'Organization details: {org_list[0]}') 19 print(f'Organization ID: {org_list[0].id}') 20 except Exception as error: 21 print(f'Error listing organizations: {error}') 22 # Implement appropriate error handling for your use case ``` * Go Go ```go 1 package main 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 8 "github.com/scalekit/scalekit-go" 9 ) 10 11 // Initialize Scalekit client with environment variables 12 // Security: Always load credentials from environment, not hardcoded 13 scalekitClient := scalekit.NewScalekitClient( 14 os.Getenv("SCALEKIT_ENVIRONMENT_URL"), 15 os.Getenv("SCALEKIT_CLIENT_ID"), 16 os.Getenv("SCALEKIT_CLIENT_SECRET"), 17 ) 18 19 // Use case: Get specific organization for directory sync operations 20 // Examples: Targeted user provisioning, organization-specific workflows 21 organization, err := scalekitClient.Organization.GetOrganization( 22 ctx, 23 organizationId, 24 ) 25 if err != nil { 26 // Handle error appropriately for your application 27 return fmt.Errorf("failed to get organization: %w", err) 28 } ``` * Java Java ```java 1 import com.scalekit.ScalekitClient; 2 3 // Initialize Scalekit client with environment variables 4 // Security: Use System.getenv() to securely access credentials 5 ScalekitClient scalekitClient = new ScalekitClient( 6 System.getenv("SCALEKIT_ENVIRONMENT_URL"), 7 System.getenv("SCALEKIT_CLIENT_ID"), 8 System.getenv("SCALEKIT_CLIENT_SECRET") 9 ); 10 11 try { 12 // Use case: List organizations for automated provisioning workflows 13 // Examples: Enterprise customer setup, multi-tenant management 14 ListOrganizationsResponse organizations = scalekitClient.organizations() 15 .listOrganizations(5, ""); 16 17 if (!organizations.getOrganizations().isEmpty()) { 18 Organization firstOrg = organizations.getOrganizations().get(0); 19 System.out.println("Organization name: " + firstOrg.getDisplayName()); 20 System.out.println("Organization ID: " + firstOrg.getId()); 21 } 22 } catch (ScalekitException error) { 23 System.err.println("Failed to list organizations: " + error.getMessage()); 24 // Implement appropriate error handling 25 } ``` 3. ### Retrieve a directory [Section titled “Retrieve a directory”](#retrieve-a-directory) After successfully listing organizations, you’ll need to retrieve the specific directory to begin syncing user and group data. You can retrieve directories using either the organization and directory IDs, or fetch the primary directory for an organization. * Node.js Node.js ```javascript 1 try { 2 // Use case: Get specific directory when organization has multiple directories 3 // Examples: Department-specific provisioning, multi-division companies 4 const { directory } = await scalekit.directory.getDirectory('', ''); 5 console.log(`Directory name: ${directory.name}`); 6 7 // Use case: Get primary directory for simple provisioning workflows 8 // Examples: Small organizations, single-directory setups 9 const { directory } = await scalekit.directory.getPrimaryDirectoryByOrganizationId(''); 10 console.log(`Primary directory ID: ${directory.id}`); 11 } catch (error) { 12 console.error('Failed to retrieve directory:', error); 13 // Handle error appropriately for your application 14 } ``` * Python Python ```python 1 try: 2 # Use case: Access specific directory for targeted user sync operations 3 # Examples: Regional offices, business unit-specific provisioning 4 directory = scalekit_client.directory.get_directory( 5 organization_id='', directory_id='' 6 ) 7 print(f'Directory name: {directory.name}') 8 9 # Use case: Get primary directory for streamlined user management 10 # Examples: Standard employee provisioning, main company directory 11 primary_directory = scalekit_client.directory.get_primary_directory_by_organization_id( 12 organization_id='' 13 ) 14 print(f'Primary directory ID: {primary_directory.id}') 15 except Exception as error: 16 print(f'Error retrieving directory: {error}') 17 # Implement appropriate error handling ``` * Go Go ```go 1 // Use case: Retrieve specific directory for granular access control 2 // Examples: Multi-tenant environments, department-level provisioning 3 directory, err := scalekitClient.Directory().GetDirectory(ctx, organizationId, directoryId) 4 if err != nil { 5 return fmt.Errorf("failed to get directory: %w", err) 6 } 7 fmt.Printf("Directory name: %s\n", directory.Name) 8 9 // Use case: Get primary directory for simplified user management 10 // Examples: Automated provisioning workflows, bulk user imports 11 directory, err := scalekitClient.Directory().GetPrimaryDirectoryByOrganizationId(ctx, organizationId) 12 if err != nil { 13 return fmt.Errorf("failed to get primary directory: %w", err) 14 } 15 fmt.Printf("Primary directory ID: %s\n", directory.ID) ``` * Java Java ```java 1 try { 2 // Use case: Access specific directory for detailed user management 3 // Examples: Custom provisioning logic, directory-specific rules 4 Directory directory = scalekitClient.directories() 5 .getDirectory("", ""); 6 System.out.println("Directory name: " + directory.getName()); 7 8 // Use case: Get primary directory for standard provisioning workflows 9 // Examples: Employee onboarding, automated user sync 10 Directory primaryDirectory = scalekitClient.directories() 11 .getPrimaryDirectoryByOrganizationId(""); 12 System.out.println("Primary directory ID: " + primaryDirectory.getId()); 13 } catch (ScalekitException error) { 14 System.err.println("Failed to retrieve directory: " + error.getMessage()); 15 // Implement appropriate error handling 16 } ``` 4. ### List users in a directory [Section titled “List users in a directory”](#list-users-in-a-directory) Once you have the directory information, you can fetch users within that directory. This is commonly used for bulk user synchronization and maintaining an up-to-date user database. * Node.js Node.js ```javascript 1 try { 2 // Use case: Bulk user synchronization and provisioning 3 // Examples: New customer onboarding, scheduled user data sync 4 const { users } = await scalekit.directory.listDirectoryUsers('', ''); 5 6 // Process each user for provisioning or updates 7 users.forEach(user => { 8 console.log(`User email: ${user.email}, Name: ${user.name}`); 9 // TODO: Implement your user provisioning logic here 10 }); 11 } catch (error) { 12 console.error('Failed to list directory users:', error); 13 // Handle error appropriately for your application 14 } ``` * Python Python ```python 1 try: 2 # Use case: Automated user provisioning workflows 3 # Examples: HR system integration, bulk user imports 4 directory_users = scalekit_client.directory.list_directory_users( 5 organization_id='', directory_id='' 6 ) 7 8 # Process each user for local database updates 9 for user in directory_users: 10 print(f'User email: {user.email}, Name: {user.name}') 11 # TODO: Implement your user synchronization logic here 12 except Exception as error: 13 print(f'Error listing directory users: {error}') 14 # Implement appropriate error handling ``` * Go Go ```go 1 // Configure pagination options for large user directories 2 options := &ListDirectoryUsersOptions{ 3 PageSize: 50, // Adjust based on your needs 4 PageToken: "", 5 } 6 7 // Use case: Paginated user retrieval for large directories 8 // Examples: Enterprise customer provisioning, regular sync jobs 9 directoryUsers, err := scalekitClient.Directory().ListDirectoryUsers(ctx, organizationId, directoryId, options) 10 if err != nil { 11 return fmt.Errorf("failed to list directory users: %w", err) 12 } 13 14 // Process each user 15 for _, user := range directoryUsers.Users { 16 fmt.Printf("User email: %s, Name: %s\n", user.Email, user.Name) 17 // TODO: Implement your user provisioning logic 18 } ``` * Java Java ```java 1 // Configure options for user listing with pagination 2 var options = ListDirectoryResourceOptions.builder() 3 .pageSize(50) // Adjust based on your requirements 4 .pageToken("") 5 .includeDetail(true) // Include detailed user information 6 .build(); 7 8 try { 9 // Use case: Enterprise user management and synchronization 10 // Examples: Scheduled sync tasks, user provisioning automation 11 ListDirectoryUsersResponse usersResponse = scalekitClient.directories() 12 .listDirectoryUsers(directory.getId(), organizationId, options); 13 14 // Process each user for provisioning 15 for (User user : usersResponse.getUsers()) { 16 System.out.println("User email: " + user.getEmail() + ", Name: " + user.getName()); 17 // TODO: Implement your user provisioning logic here 18 } 19 } catch (ScalekitException error) { 20 System.err.println("Failed to list directory users: " + error.getMessage()); 21 // Implement appropriate error handling 22 } ``` 5. ### List groups in a directory [Section titled “List groups in a directory”](#list-groups-in-a-directory) Groups are essential for implementing role-based access control (RBAC) in your application. After retrieving users, you can fetch groups to manage permissions and access levels based on organizational structure. * Node.js Node.js ```javascript 1 try { 2 // Use case: Role-based access control implementation 3 // Examples: Department-level permissions, project-based access 4 const { groups } = await scalekit.directory.listDirectoryGroups( 5 '', 6 '', 7 ); 8 9 // Process each group for RBAC setup 10 groups.forEach(group => { 11 console.log(`Group name: ${group.name}, ID: ${group.id}`); 12 // TODO: Implement your group-based permission logic here 13 }); 14 } catch (error) { 15 console.error('Failed to list directory groups:', error); 16 // Handle error appropriately for your application 17 } ``` * Python Python ```python 1 try: 2 # Use case: Department-based access control 3 # Examples: Engineering vs Sales permissions, project team access 4 directory_groups = scalekit_client.directory.list_directory_groups( 5 directory_id='', organization_id='' 6 ) 7 8 # Process each group for permission mapping 9 for group in directory_groups: 10 print(f'Group name: {group.name}, ID: {group.id}') 11 # TODO: Implement your group-based permission logic here 12 except Exception as error: 13 print(f'Error listing directory groups: {error}') 14 # Implement appropriate error handling ``` * Go Go ```go 1 // Configure pagination for group listing 2 options := &ListDirectoryGroupsOptions{ 3 PageSize: 25, // Adjust based on expected group count 4 PageToken: "", 5 } 6 7 // Use case: Organizational role management 8 // Examples: Enterprise role hierarchy, department-based access 9 directoryGroups, err := scalekitClient.Directory().ListDirectoryGroups(ctx, organizationId, directoryId, options) 10 if err != nil { 11 return fmt.Errorf("failed to list directory groups: %w", err) 12 } 13 14 // Process each group for RBAC implementation 15 for _, group := range directoryGroups.Groups { 16 fmt.Printf("Group name: %s, ID: %s\n", group.Name, group.ID) 17 // TODO: Implement your group-based permission logic 18 } ``` * Java Java ```java 1 // Configure options for detailed group information 2 var options = ListDirectoryResourceOptions.builder() 3 .pageSize(25) // Adjust based on your requirements 4 .pageToken("") 5 .includeDetail(true) // Include group membership details 6 .build(); 7 8 try { 9 // Use case: Enterprise permission management 10 // Examples: Role assignments, access level configurations 11 ListDirectoryGroupsResponse groupsResponse = scalekitClient.directories() 12 .listDirectoryGroups(directory.getId(), organizationId, options); 13 14 // Process each group for permission mapping 15 for (Group group : groupsResponse.getGroups()) { 16 System.out.println("Group name: " + group.getName() + ", ID: " + group.getId()); 17 // TODO: Implement your group-based permission logic here 18 } 19 } catch (ScalekitException error) { 20 System.err.println("Failed to list directory groups: " + error.getMessage()); 21 // Implement appropriate error handling 22 } ``` Scalekit’s Directory API provides a simple way to fetch user and group information on-demand. Refer to our [API reference](https://docs.scalekit.com/apis/) to explore more capabilities. ## Realtime user provisioning with webhooks [Section titled “Realtime user provisioning with webhooks”](#realtime-user-provisioning-with-webhooks) While the Directory API is perfect for scheduled synchronization, webhooks enable immediate, real-time user provisioning. When directory providers send events to Scalekit, we forward them instantly to your application, allowing you to respond to user changes as they happen. This approach is ideal for scenarios requiring immediate action, such as new employee onboarding or emergency access revocation. 1. ### Create a secure webhook endpoint [Section titled “Create a secure webhook endpoint”](#create-a-secure-webhook-endpoint) Create a webhook endpoint to receive real-time events from directory providers. After implementing your endpoint, register it in **Dashboard > Webhooks** where you’ll receive a secret for payload verification. Critical security requirement Always verify webhook signatures before processing events. This prevents unauthorized parties from triggering your provisioning logic and protects against replay attacks. * Node.js Express.js ```javascript 1 app.post('/webhook', async (req, res) => { 2 // Security: ALWAYS verify requests are from Scalekit before processing 3 // This prevents unauthorized parties from triggering your provisioning logic 4 5 const event = req.body; 6 const headers = req.headers; 7 const secret = process.env.SCALEKIT_WEBHOOK_SECRET; 8 9 try { 10 // Verify webhook signature to prevent replay attacks and forged requests 11 await scalekit.verifyWebhookPayload(secret, headers, event); 12 } catch (error) { 13 console.error('Webhook signature verification failed:', error); 14 // Return 400 for invalid signatures - this prevents processing malicious requests 15 return res.status(400).json({ error: 'Invalid signature' }); 16 } 17 18 try { 19 // Use case: Real-time user provisioning based on directory events 20 // Examples: New hire onboarding, emergency access revocation, role changes 21 const { email, name } = event.data; 22 23 // Process the webhook event based on its type 24 switch (event.type) { 25 case 'organization.directory.user_created': 26 await createUserAccount(email, name); 27 break; 28 case 'organization.directory.user_updated': 29 await updateUserAccount(email, name); 30 break; 31 case 'organization.directory.user_deleted': 32 await deactivateUserAccount(email); 33 break; 34 default: 35 console.log(`Unhandled event type: ${event.type}`); 36 } 37 38 res.status(201).json({ message: 'Webhook processed successfully' }); 39 } catch (processingError) { 40 console.error('Failed to process webhook event:', processingError); 41 res.status(500).json({ error: 'Processing failed' }); 42 } 43 }); ``` * Python FastAPI ```python 1 from fastapi import FastAPI, Request, HTTPException 2 import os 3 import json 4 5 app = FastAPI() 6 7 @app.post("/webhook") 8 async def api_webhook(request: Request): 9 # Security: ALWAYS verify webhook signatures before processing events 10 # This prevents unauthorized webhook calls and replay attacks 11 12 headers = request.headers 13 body = await request.json() 14 15 try: 16 # Verify webhook payload using the secret from Scalekit dashboard 17 # Get this from Dashboard > Webhooks after registering your endpoint 18 is_valid = scalekit_client.verify_webhook_payload( 19 secret=os.getenv("SCALEKIT_WEBHOOK_SECRET"), 20 headers=headers, 21 payload=json.dumps(body).encode('utf-8') 22 ) 23 24 if not is_valid: 25 raise HTTPException(status_code=400, detail="Invalid webhook signature") 26 27 except Exception as verification_error: 28 print(f"Webhook verification failed: {verification_error}") 29 raise HTTPException(status_code=400, detail="Webhook verification failed") 30 31 # Use case: Instant user provisioning based on directory events 32 # Examples: Automated onboarding, immediate access revocation, role updates 33 try: 34 event_type = body.get("type") 35 event_data = body.get("data", {}) 36 email = event_data.get("email") 37 name = event_data.get("name") 38 39 if event_type == "organization.directory.user_created": 40 await create_user_account(email, name) 41 elif event_type == "organization.directory.user_updated": 42 await update_user_account(email, name) 43 elif event_type == "organization.directory.user_deleted": 44 await deactivate_user_account(email) 45 46 return JSONResponse(status_code=201, content={"status": "processed"}) 47 48 except Exception as processing_error: 49 print(f"Failed to process webhook: {processing_error}") 50 raise HTTPException(status_code=500, detail="Event processing failed") ``` * Java Spring Boot ```java 1 @PostMapping("/webhook") 2 public ResponseEntity webhook( 3 @RequestBody String body, 4 @RequestHeader Map headers) { 5 6 // Security: ALWAYS verify webhook signatures before processing 7 // This prevents malicious webhook calls and protects against replay attacks 8 9 String secret = System.getenv("SCALEKIT_WEBHOOK_SECRET"); 10 11 try { 12 // Verify webhook signature using Scalekit SDK 13 boolean isValid = scalekitClient.webhook() 14 .verifyWebhookPayload(secret, headers, body.getBytes()); 15 16 if (!isValid) { 17 return ResponseEntity.badRequest().body("Invalid webhook signature"); 18 } 19 20 } catch (Exception verificationError) { 21 System.err.println("Webhook verification failed: " + verificationError.getMessage()); 22 return ResponseEntity.badRequest().body("Webhook verification failed"); 23 } 24 25 try { 26 // Use case: Real-time user lifecycle management 27 // Examples: Employee onboarding, access termination, role modifications 28 ObjectMapper mapper = new ObjectMapper(); 29 JsonNode rootNode = mapper.readTree(body); 30 31 String eventType = rootNode.get("type").asText(); 32 JsonNode data = rootNode.get("data"); 33 34 switch (eventType) { 35 case "organization.directory.user_created": 36 String email = data.get("email").asText(); 37 String name = data.get("name").asText(); 38 createUserAccount(email, name); 39 break; 40 case "organization.directory.user_updated": 41 updateUserAccount(data); 42 break; 43 case "organization.directory.user_deleted": 44 deactivateUserAccount(data.get("email").asText()); 45 break; 46 default: 47 System.out.println("Unhandled event type: " + eventType); 48 } 49 50 return ResponseEntity.status(HttpStatus.CREATED).body("Webhook processed"); 51 52 } catch (Exception processingError) { 53 System.err.println("Failed to process webhook event: " + processingError.getMessage()); 54 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) 55 .body("Event processing failed"); 56 } 57 } ``` * Go Go ```go 1 // Security: Store webhook secret securely in environment variables 2 // Get this from Dashboard > Webhooks after registering your endpoint 3 webhookSecret := os.Getenv("SCALEKIT_WEBHOOK_SECRET") 4 5 http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) { 6 // Security: ALWAYS verify webhook signatures before processing events 7 // This prevents unauthorized webhook calls and replay attacks 8 9 if r.Method != http.MethodPost { 10 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 11 return 12 } 13 14 body, err := io.ReadAll(r.Body) 15 if err != nil { 16 http.Error(w, err.Error(), http.StatusBadRequest) 17 return 18 } 19 defer r.Body.Close() 20 21 // Extract webhook headers for verification 22 headers := map[string]string{ 23 "webhook-id": r.Header.Get("webhook-id"), 24 "webhook-signature": r.Header.Get("webhook-signature"), 25 "webhook-timestamp": r.Header.Get("webhook-timestamp"), 26 } 27 28 // Verify webhook signature to prevent malicious requests 29 _, err = scalekitClient.VerifyWebhookPayload(webhookSecret, headers, body) 30 if err != nil { 31 http.Error(w, "Invalid webhook signature", http.StatusBadRequest) 32 return 33 } 34 35 // Use case: Instant user provisioning and lifecycle management 36 // Examples: Real-time onboarding, emergency access revocation, role synchronization 37 var webhookEvent WebhookEvent 38 if err := json.Unmarshal(body, &webhookEvent); err != nil { 39 http.Error(w, "Invalid webhook payload", http.StatusBadRequest) 40 return 41 } 42 43 switch webhookEvent.Type { 44 case "organization.directory.user_created": 45 err = createUserAccount(webhookEvent.Data.Email, webhookEvent.Data.Name) 46 case "organization.directory.user_updated": 47 err = updateUserAccount(webhookEvent.Data) 48 case "organization.directory.user_deleted": 49 err = deactivateUserAccount(webhookEvent.Data.Email) 50 default: 51 fmt.Printf("Unhandled event type: %s\n", webhookEvent.Type) 52 } 53 54 if err != nil { 55 http.Error(w, "Failed to process webhook", http.StatusInternalServerError) 56 return 57 } 58 59 w.WriteHeader(http.StatusCreated) 60 w.Write([]byte(`{"status": "processed"}`)) 61 }) ``` 2. ### Register your webhook endpoint [Section titled “Register your webhook endpoint”](#register-your-webhook-endpoint) After implementing your secure webhook endpoint, register it in the Scalekit dashboard to start receiving events: 1. Navigate to **Dashboard > Webhooks** 2. Click **+Add Endpoint** 3. Enter your webhook endpoint URL (e.g., `https://your-app.com/api/webhooks/scalekit`) 4. Add a meaningful description for your reference 5. Select the event types you want to receive. Common choices include: * `organization.directory.user_created` - New user provisioning * `organization.directory.user_updated` - User profile changes * `organization.directory.user_deleted` - User deactivation * `organization.directory.group_created` - New group creation * `organization.directory.group_updated` - Group modifications Once registered, your webhook endpoint will start receiving event payloads from directory providers in real-time. 3. ### Process webhook events [Section titled “Process webhook events”](#process-webhook-events) Scalekit standardizes event payloads across different directory providers, ensuring consistent data structure regardless of whether your customers use Azure AD, Okta, Google Workspace, or other providers. When directory changes occur, Scalekit sends events with the following structure: Webhook event payload ```json 1 { 2 "id": "evt_1234567890", 3 "type": "organization.directory.user_created", 4 "data": { 5 "email": "john.doe@company.com", 6 "name": "John Doe", 7 "organization_id": "org_12345", 8 "directory_id": "dir_67890" 9 }, 10 "timestamp": "2024-01-15T10:30:00Z" 11 } ``` You have now successfully implemented and registered a webhook endpoint, enabling your application to receive real-time events for automated user provisioning. Your system can now respond instantly to directory changes, providing seamless user lifecycle management. Refer to our [webhook implementation guide](/authenticate/implement-workflows/implement-webhooks/) for the complete list of available event types and payload structures.
---
# DOCUMENT BOUNDARY
---
# Modular SSO quickstart
> Enable enterprise SSO for any customer in minutes with built-in SAML and OIDC integrations
Enterprise customers often require Single Sign-On (SSO) support for their applications. Rather than building custom integrations for every identity provider such as Okta, Entra ID, or JumpCloud and managing their OIDC and SAML protocols, you can let Scalekit handle those connections with each of your customer’s identity providers. Modular SSO is designed for applications that maintain their own user database and session management. This lightweight integration focuses solely on identity verification, giving you complete control over user data and authentication flows. Choose Modular SSO when you: * Want to manage user records in your own database * Prefer to implement custom session management logic * Need to integrate SSO without changing your existing authentication architecture * Already have existing user management infrastructure ### Build with a coding agent * Claude Code ```bash /plugin marketplace add scalekit-inc/claude-code-authstack ``` ```bash /plugin install modular-sso@scalekit-auth-stack ``` * GitHub Copilot CLI ```bash copilot plugin marketplace add scalekit-inc/github-copilot-authstack ``` ```bash copilot plugin install modular-sso@scalekit-auth-stack ``` * 40+ agents ```bash npx skills add scalekit-inc/skills --skill modular-sso ``` [Continue building with AI →](/dev-kit/build-with-ai/sso/) 1. ## Set up Scalekit [Section titled “Set up Scalekit”](#set-up-scalekit) Use the following instructions to install the SDK for your technology stack. * Node.js ```bash npm install @scalekit-sdk/node ``` * Python ```sh pip install scalekit-sdk-python ``` * Go ```sh go get -u github.com/scalekit-inc/scalekit-sdk-go ``` * Java ```groovy /* Gradle users - add the following to your dependencies in build file */ implementation "com.scalekit:scalekit-sdk-java:2.0.11" ``` ```xml com.scalekit scalekit-sdk-java 2.0.11 ``` Since we will using Modular SSO, you need to disable complete auth: 1. Go to Dashboard > Authentication > General 2. Under “Full-Stack Auth” section, click “Disable Full-Stack Auth” Now you’re ready to start integrating SSO into your app! 2. ## Redirect the users to their enterprise identity provider login page [Section titled “Redirect the users to their enterprise identity provider login page”](#redirect-the-users-to-their-enterprise-identity-provider-login-page) Use the Scalekit SDK to construct authorization URL with your redirect URI and required scopes. Scalekit will automatically redirect the user to the user’s enterprise identity provider login page to authenticate. * Node.js authorization-url.js ```javascript 1 import { Scalekit } from '@scalekit-sdk/node'; 2 3 const scalekit = new ScalekitClient( 4 '', // Your Scalekit environment URL 5 '', // Unique identifier for your app 6 '', 7 ); 8 9 const options = {}; 10 11 // Specify which SSO connection to use (choose one based on your use case) 12 // These identifiers are evaluated in order of precedence: 13 14 // 1. connectionId (highest precedence) - Use when you know the exact SSO connection 15 options['connectionId'] = 'conn_15696105471768821'; 16 17 // 2. organizationId - Routes to organization's SSO (useful for multi-tenant apps) 18 // If org has multiple connections, the first active one is selected 19 options['organizationId'] = 'org_15421144869927830'; 20 21 // 3. loginHint (lowest precedence) - Extracts domain from email to find connection 22 // Domain must be registered to the organization (manually via Dashboard or through admin portal during enterprise onboarding) 23 options['loginHint'] = 'user@example.com'; 24 25 // redirect_uri: Your callback endpoint that receives the authorization code 26 // Must match the URL registered in your Scalekit dashboard 27 const redirectUrl = 'https://your-app.com/auth/callback'; 28 29 const authorizationURL = scalekit.getAuthorizationUrl(redirectUrl, options); 30 // Redirect user to this URL to begin SSO authentication ``` * Python authorization\_url.py ```python 1 from scalekit import ScalekitClient, AuthorizationUrlOptions 2 3 scalekit = ScalekitClient( 4 '', # Your Scalekit environment URL 5 '', # Unique identifier for your app 6 '' 7 ) 8 9 options = AuthorizationUrlOptions() 10 11 # Specify which SSO connection to use (choose one based on your use case) 12 # These identifiers are evaluated in order of precedence: 13 14 # 1. connection_id (highest precedence) - Use when you know the exact SSO connection 15 options.connection_id = 'conn_15696105471768821' 16 17 # 2. organization_id - Routes to organization's SSO (useful for multi-tenant apps) 18 # If org has multiple connections, the first active one is selected 19 options.organization_id = 'org_15421144869927830' 20 21 # 3. login_hint (lowest precedence) - Extracts domain from email to find connection 22 # Domain must be registered to the organization (manually via Dashboard or through admin portal during enterprise onboarding) 23 options.login_hint = 'user@example.com' 24 25 # redirect_uri: Your callback endpoint that receives the authorization code 26 # Must match the URL registered in your Scalekit dashboard 27 redirect_uri = 'https://your-app.com/auth/callback' 28 29 authorization_url = scalekit_client.get_authorization_url( 30 redirect_uri=redirect_uri, 31 options=options 32 ) 33 # Redirect user to this URL to begin SSO authentication ``` * Go authorization\_url.go ```go 1 import ( 2 "github.com/scalekit-inc/scalekit-sdk-go" 3 ) 4 5 func main() { 6 scalekitClient := scalekit.NewScalekitClient( 7 "", // Your Scalekit environment URL 8 "", // Unique identifier for your app 9 "" 10 ) 11 12 options := scalekitClient.AuthorizationUrlOptions{} 13 14 // Specify which SSO connection to use (choose one based on your use case) 15 // These identifiers are evaluated in order of precedence: 16 17 // 1. ConnectionId (highest precedence) - Use when you know the exact SSO connection 18 options.ConnectionId = "conn_15696105471768821" 19 20 // 2. OrganizationId - Routes to organization's SSO (useful for multi-tenant apps) 21 // If org has multiple connections, the first active one is selected 22 options.OrganizationId = "org_15421144869927830" 23 24 // 3. LoginHint (lowest precedence) - Extracts domain from email to find connection 25 // Domain must be registered to the organization (manually via Dashboard or through admin portal during enterprise onboarding) 26 options.LoginHint = "user@example.com" 27 28 // redirectUrl: Your callback endpoint that receives the authorization code 29 // Must match the URL registered in your Scalekit dashboard 30 redirectUrl := "https://your-app.com/auth/callback" 31 32 authorizationURL := scalekitClient.GetAuthorizationUrl( 33 redirectUrl, 34 options, 35 ) 36 // Redirect user to this URL to begin SSO authentication 37 } ``` * Java AuthorizationUrl.java ```java 1 package com.scalekit; 2 3 import com.scalekit.ScalekitClient; 4 import com.scalekit.internal.http.AuthorizationUrlOptions; 5 6 public class Main { 7 8 public static void main(String[] args) { 9 ScalekitClient scalekitClient = new ScalekitClient( 10 "", // Your Scalekit environment URL 11 "", // Unique identifier for your app 12 "" 13 ); 14 15 AuthorizationUrlOptions options = new AuthorizationUrlOptions(); 16 17 // Specify which SSO connection to use (choose one based on your use case) 18 // These identifiers are evaluated in order of precedence: 19 20 // 1. connectionId (highest precedence) - Use when you know the exact SSO connection 21 options.setConnectionId("con_13388706786312310"); 22 23 // 2. organizationId - Routes to organization's SSO (useful for multi-tenant apps) 24 // If org has multiple connections, the first active one is selected 25 options.setOrganizationId("org_13388706786312310"); 26 27 // 3. loginHint (lowest precedence) - Extracts domain from email to find connection 28 // Domain must be registered to the organization (manually via Dashboard or through admin portal during enterprise onboarding) 29 options.setLoginHint("user@example.com"); 30 31 // redirectUrl: Your callback endpoint that receives the authorization code 32 // Must match the URL registered in your Scalekit dashboard 33 String redirectUrl = "https://your-app.com/auth/callback"; 34 35 try { 36 String url = scalekitClient 37 .authentication() 38 .getAuthorizationUrl(redirectUrl, options) 39 .toString(); 40 // Redirect user to this URL to begin SSO authentication 41 } catch (Exception e) { 42 System.out.println(e.getMessage()); 43 } 44 } 45 } ``` * Direct URL (No SDK) OAuth2 authorization URL ```sh /oauth/authorize? response_type=code& # OAuth2 authorization code flow client_id=& # Your Scalekit client ID redirect_uri=& # URL-encoded callback URL scope=openid profile email& # "offline_access" is required to receive a refresh token organization_id=org_15421144869927830& # (Optional) Route by organization connection_id=conn_15696105471768821& # (Optional) Specific SSO connection login_hint=user@example.com # (Optional) Extract domain from email ``` **SSO identifiers** (choose one or more, evaluated in order of precedence): * `connection_id` - Direct to specific SSO connection (highest precedence) * `organization_id` - Route to organization’s SSO * `domain_hint` - Lookup connection by domain * `login_hint` - Extract domain from email (lowest precedence). Domain must be registered to the organization (manually via Dashboard or through admin portal when [onboarding an enterprise customer](/sso/guides/onboard-enterprise-customers/)) Example with actual values ```http https://tinotat-dev.scalekit.dev/oauth/authorize? response_type=code& client_id=skc_88036702639096097& redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fcallback& scope=openid%20profile%20email& organization_id=org_15421144869927830 ``` Enterprise users see their identity provider’s login page. Users verify their identity through the authentication policies set by their organization’s administrator. Post successful verification, the user profile is [normalized](/sso/guides/user-profile-details/) and sent to your app. If your application needs to verify whether an SSO connection exists for a specific domain before proceeding, you can use the [list connections by domain SDK method](/guides/user-auth/check-sso-domain/). For details on how Scalekit determines which SSO connection to use, refer to the [SSO identifier precedence rules](/sso/guides/authorization-url/#parameter-precedence). 3. ## Get user details from the callback [Section titled “Get user details from the callback”](#get-user-details-from-the-callback) After successful authentication, Scalekit redirects to your callback URL with an authorization code. Your application exchanges this code for the user’s profile information and session tokens. 1. Add a callback endpoint in your application (typically `https://your-app.com/auth/callback`) 2. [Register](/guides/dashboard/redirects/#allowed-callback-urls) it in your Scalekit dashboard > Authentication > Redirect URLS > Allowed Callback URLs In authentication flow, Scalekit redirects to your callback URL with an authorization code. Your application exchanges this code for the user’s profile information. * Node.js Fetch user profile ```javascript 1 // Extract authentication parameters from the callback request 2 const { 3 code, 4 error, 5 error_description, 6 idp_initiated_login, 7 connection_id, 8 relay_state 9 } = req.query; 10 11 if (error) { 12 // Handle authentication errors returned from the identity provider 13 } 14 15 // Recommended: Process IdP-initiated login flows (when users start from their SSO portal) 16 17 const result = await scalekit.authenticateWithCode(code, redirectUri); 18 const userEmail = result.user.email; 19 20 // Create a session for the authenticated user and grant appropriate access permissions ``` * Python Fetch user profile ```py 1 # Extract authentication parameters from the callback request 2 code = request.args.get('code') 3 error = request.args.get('error') 4 error_description = request.args.get('error_description') 5 idp_initiated_login = request.args.get('idp_initiated_login') 6 connection_id = request.args.get('connection_id') 7 relay_state = request.args.get('relay_state') 8 9 if error: 10 raise Exception(error_description) 11 12 # Recommended: Process IdP-initiated login flows (when users start from their SSO portal) 13 14 result = scalekit.authenticate_with_code(code, '') 15 16 # Access normalized user profile information 17 user_email = result.user.email 18 19 # Create a session for the authenticated user and grant appropriate access permissions ``` * Go Fetch user profile ```go 1 // Extract authentication parameters from the callback request 2 code := r.URL.Query().Get("code") 3 error := r.URL.Query().Get("error") 4 errorDescription := r.URL.Query().Get("error_description") 5 idpInitiatedLogin := r.URL.Query().Get("idp_initiated_login") 6 connectionID := r.URL.Query().Get("connection_id") 7 relayState := r.URL.Query().Get("relay_state") 8 9 if error != "" { 10 // Handle authentication errors returned from the identity provider 11 } 12 13 // Recommended: Process IdP-initiated login flows (when users start from their SSO portal) 14 15 result, err := scalekitClient.AuthenticateWithCode(r.Context(), code, redirectUrl) 16 17 if err != nil { 18 // Handle token exchange or validation errors 19 } 20 21 // Access normalized user profile information 22 userEmail := result.User.Email 23 24 // Create a session for the authenticated user and grant appropriate access permissions ``` * Java Fetch user profile ```java 1 // Extract authentication parameters from the callback request 2 String code = request.getParameter("code"); 3 String error = request.getParameter("error"); 4 String errorDescription = request.getParameter("error_description"); 5 String idpInitiatedLogin = request.getParameter("idp_initiated_login"); 6 String connectionID = request.getParameter("connection_id"); 7 String relayState = request.getParameter("relay_state"); 8 9 if (error != null && !error.isEmpty()) { 10 // Handle authentication errors returned from the identity provider 11 return; 12 } 13 14 // Recommended: Process IdP-initiated login flows (when users start from their SSO portal) 15 16 try { 17 AuthenticationResponse result = scalekit.authentication().authenticateWithCode(code, redirectUrl); 18 String userEmail = result.getIdTokenClaims().getEmail(); 19 20 // Create a session for the authenticated user and grant appropriate access permissions 21 } catch (Exception e) { 22 // Handle token exchange or validation errors 23 } ``` The `result` object * Node.js Validate tokens ```js 1 // Validate and decode the ID token from the authentication result 2 const idTokenClaims = await scalekit.validateToken(result.idToken); 3 4 // Validate and decode the access token 5 const accessTokenClaims = await scalekit.validateToken(result.accessToken); ``` * Python Validate tokens ```py 1 # Validate and decode the ID token from the authentication result 2 id_token_claims = scalekit_client.validate_token(result["id_token"]) 3 4 # Validate and decode the access token 5 access_token_claims = scalekit_client.validate_token(result["access_token"]) ``` * Go Validate tokens ```go 1 // Create a background context for the API call 2 ctx := context.Background() 3 4 // Validate and decode the access token (uses JWKS from the client) 5 accessTokenClaims, err := scalekitClient.GetAccessTokenClaims(ctx, result.AccessToken) 6 if err != nil { 7 // handle error 8 } ``` * Java Validate tokens ```java 1 // Validate and decode the ID token 2 Map idTokenClaims = scalekitClient.validateToken(result.getIdToken()); 3 4 // Validate and decode the access token 5 Map accessTokenClaims = scalekitClient.validateToken(result.getAccessToken()); ``` - Auth result ```js 1 { 2 user: { 3 email: 'john@example.com', 4 familyName: 'Doe', 5 givenName: 'John', 6 username: 'john@example.com', 7 id: 'conn_70087756662964366;dcc62570-6a5a-4819-b11b-d33d110c7716' 8 }, 9 idToken: 'eyJhbGciOiJSU..bcLQ', 10 accessToken: 'eyJhbGciO..', 11 expiresIn: 899 12 } ``` - ID token (decoded) ```js 1 { 2 iss: '', // Issuer: Scalekit environment URL (must match your environment) 3 aud: [ 'skc_70087756327420046' ], // Audience: Your client ID (must match for validation) 4 azp: 'skc_70087756327420046', // Authorized party: Usually same as aud 5 sub: 'conn_70087756662964366;e964d135-35c7-4a13-a3b4-2579a1cdf4e6', // Subject: Connection ID and IdP user ID (SSO-specific format) 6 oid: 'org_70087756646187150', // Organization ID: User's organization 7 exp: 1758952038, // Expiration: Unix timestamp (validate token hasn't expired) 8 iat: 1758692838, // Issued at: Unix timestamp when token was issued 9 at_hash: 'yMGIBg7BkmIGgD6_dZPEGQ', // Access token hash: For token binding validation 10 c_hash: '4x7qsXnlRw6dRC6twnuENw', // Authorization code hash: For code binding validation 11 amr: [ 'conn_70087756662964366' ], // Authentication method reference: SSO connection ID used for authentication 12 email: 'john@example.com', // User's email address 13 preferred_username: 'john@example.com', // Preferred username (often same as email for SSO) 14 family_name: 'Doe', // User's last name 15 given_name: 'John', // User's first name 16 sid: 'ses_91646612652163629', // Session ID: Links token to user session 17 client_id: 'skc_70087756327420046' // Client ID: Your application identifier 18 } ``` - Access token (decoded) ```js 1 { 2 "iss": "", // Issuer: Scalekit environment URL (must match your environment) 3 "aud": ["skc_70087756327420046"], // Audience: Your client ID (must match for validation) 4 "sub": "conn_70087756662964366;dcc62570-6a5a-4819-b11b-d33d110c7716", // Subject: Connection ID and IdP user ID (SSO-specific format) 5 "exp": 1758693916, // Expiration: Unix timestamp (validate token hasn't expired) 6 "iat": 1758693016, // Issued at: Unix timestamp when token was issued 7 "nbf": 1758693016, // Not before: Unix timestamp (token valid from this time) 8 "jti": "tkn_91646913048216109", // JWT ID: Unique token identifier 9 "client_id": "skc_70087756327420046" // Client ID: Your application identifier 10 } ``` 4. ## Handle IdP-initiated SSO Recommended [Section titled “Handle IdP-initiated SSO ”](#handle-idp-initiated-sso-) When users start the login process from their identity provider’s portal (rather than your application), this is called IdP-initiated SSO. Scalekit converts these requests to secure SP-initiated flows automatically. Your initiate login endpoint receives an `idp_initiated_login` JWT parameter containing the user’s organization and connection details. Decode this token and generate a new authorization URL to complete the authentication flow securely. ```sh https://yourapp.com/login?idp_initiated_login= ``` Configure your initiate login endpoint in [Dashboard > Authentication > Redirects](/guides/dashboard/redirects/#initiate-login-url) * Node.js handle-idp-initiated.js ```javascript 1 // Your initiate login endpoint receives the IdP-initiated login token 2 const { idp_initiated_login, error, error_description } = req.query; 3 4 if (error) { 5 return res.status(400).json({ message: error_description }); 6 } 7 8 // When users start login from their IdP portal, convert to SP-initiated flow 9 if (idp_initiated_login) { 10 // Decode the JWT to extract organization and connection information 11 const claims = await scalekit.getIdpInitiatedLoginClaims(idp_initiated_login); 12 13 const options = { 14 connectionId: claims.connection_id, // Specific SSO connection 15 organizationId: claims.organization_id, // User's organization 16 loginHint: claims.login_hint, // User's email for context 17 state: claims.relay_state // Preserve state from IdP 18 }; 19 20 // Generate authorization URL and redirect to complete authentication 21 const authorizationURL = scalekit.getAuthorizationUrl( 22 'https://your-app.com/auth/callback', 23 options 24 ); 25 26 return res.redirect(authorizationURL); 27 } ``` * Python handle\_idp\_initiated.py ```python 1 # Your initiate login endpoint receives the IdP-initiated login token 2 idp_initiated_login = request.args.get('idp_initiated_login') 3 error = request.args.get('error') 4 error_description = request.args.get('error_description') 5 6 if error: 7 raise Exception(error_description) 8 9 # When users start login from their IdP portal, convert to SP-initiated flow 10 if idp_initiated_login: 11 # Decode the JWT to extract organization and connection information 12 claims = await scalekit.get_idp_initiated_login_claims(idp_initiated_login) 13 14 options = AuthorizationUrlOptions() 15 options.connection_id = claims.get('connection_id') # Specific SSO connection 16 options.organization_id = claims.get('organization_id') # User's organization 17 options.login_hint = claims.get('login_hint') # User's email for context 18 options.state = claims.get('relay_state') # Preserve state from IdP 19 20 # Generate authorization URL and redirect to complete authentication 21 authorization_url = scalekit.get_authorization_url( 22 redirect_uri='https://your-app.com/auth/callback', 23 options=options 24 ) 25 26 return redirect(authorization_url) ``` * Go handle\_idp\_initiated.go ```go 1 // Your initiate login endpoint receives the IdP-initiated login token 2 idpInitiatedLogin := r.URL.Query().Get("idp_initiated_login") 3 errorDesc := r.URL.Query().Get("error_description") 4 5 if errorDesc != "" { 6 http.Error(w, errorDesc, http.StatusBadRequest) 7 return 8 } 9 10 // When users start login from their IdP portal, convert to SP-initiated flow 11 if idpInitiatedLogin != "" { 12 // Decode the JWT to extract organization and connection information 13 claims, err := scalekitClient.GetIdpInitiatedLoginClaims(r.Context(), idpInitiatedLogin) 14 if err != nil { 15 http.Error(w, err.Error(), http.StatusInternalServerError) 16 return 17 } 18 19 options := scalekit.AuthorizationUrlOptions{ 20 ConnectionId: claims.ConnectionID, // Specific SSO connection 21 OrganizationId: claims.OrganizationID, // User's organization 22 LoginHint: claims.LoginHint, // User's email for context 23 } 24 25 // Generate authorization URL and redirect to complete authentication 26 authUrl, err := scalekitClient.GetAuthorizationUrl( 27 "https://your-app.com/auth/callback", 28 options 29 ) 30 31 if err != nil { 32 http.Error(w, err.Error(), http.StatusInternalServerError) 33 return 34 } 35 36 http.Redirect(w, r, authUrl.String(), http.StatusFound) 37 } ``` * Java HandleIdpInitiated.java ```java 1 // Your initiate login endpoint receives the IdP-initiated login token 2 @GetMapping("/login") 3 public RedirectView handleInitiateLogin( 4 @RequestParam(required = false, name = "idp_initiated_login") String idpInitiatedLoginToken, 5 @RequestParam(required = false) String error, 6 @RequestParam(required = false, name = "error_description") String errorDescription, 7 HttpServletResponse response) throws IOException { 8 9 if (error != null) { 10 response.sendError(HttpStatus.BAD_REQUEST.value(), errorDescription); 11 return null; 12 } 13 14 // When users start login from their IdP portal, convert to SP-initiated flow 15 if (idpInitiatedLoginToken != null) { 16 // Decode the JWT to extract organization and connection information 17 IdpInitiatedLoginClaims claims = scalekit 18 .authentication() 19 .getIdpInitiatedLoginClaims(idpInitiatedLoginToken); 20 21 if (claims == null) { 22 response.sendError(HttpStatus.BAD_REQUEST.value(), "Invalid token"); 23 return null; 24 } 25 26 AuthorizationUrlOptions options = new AuthorizationUrlOptions(); 27 options.setConnectionId(claims.getConnectionID()); // Specific SSO connection 28 options.setOrganizationId(claims.getOrganizationID()); // User's organization 29 options.setLoginHint(claims.getLoginHint()); // User's email for context 30 31 // Generate authorization URL and redirect to complete authentication 32 String authUrl = scalekit 33 .authentication() 34 .getAuthorizationUrl("https://your-app.com/auth/callback", options) 35 .toString(); 36 37 response.sendRedirect(authUrl); 38 return null; 39 } 40 41 return null; 42 } ``` This approach provides enhanced security by converting IdP-initiated requests to standard SP-initiated flows, protecting against SAML assertion theft and replay attacks. Learn more: [IdP-initiated SSO implementation guide](/sso/guides/idp-init-sso/) 5. ## Test your SSO integration [Section titled “Test your SSO integration”](#test-your-sso-integration) Validate your implementation using the **IdP Simulator** and **Test Organization** included in your development environment. Test all three scenarios before deploying to production. Your environment includes a pre-configured test organization (found in **Dashboard > Organizations**) with domains like `@example.com` and `@example.org` for testing. Pass one of the following connection selectors in your authorization URL: * Email address with `@example.com` or `@example.org` domain * Test organization’s connection ID * Organization ID This opens the SSO login page (IdP Simulator) that simulates your customer’s identity provider login experience.  For detailed testing instructions and scenarios, see our [Complete SSO testing guide](/sso/guides/test-sso/) 6. ## Set up SSO with your existing authentication system [Section titled “Set up SSO with your existing authentication system”](#set-up-sso-with-your-existing-authentication-system) Many applications already use an authentication provider such as Auth0, Firebase, or AWS Cognito. To enable single sign-on (SSO) using Scalekit, configure Scalekit to work with your current authentication provider. ### Auth0 Integrate Scalekit with Auth0 for enterprise SSO [Know more →](/guides/integrations/auth-systems/auth0) ### Firebase Auth Add enterprise authentication to Firebase projects [Know more →](/guides/integrations/auth-systems/firebase) ### AWS Cognito Configure Scalekit with AWS Cognito user pools [Know more →](/guides/integrations/auth-systems/aws-cognito) 7. ## Onboard enterprise customers [Section titled “Onboard enterprise customers”](#onboard-enterprise-customers) Enable SSO for your enterprise customers by creating an organization in Scalekit and providing them access to the Admin Portal. Your customers configure their identity provider settings themselves through a self-service portal. **Create an organization** for your customer in [Dashboard > Organizations](https://app.scalekit.com/organizations), then provide Admin Portal access using one of these methods: * Shareable link Generate a secure link your customer can use to access the Admin Portal: generate-portal-link.js ```javascript // Generate a one-time Admin Portal link for your customer const portalLink = await scalekit.organization.generatePortalLink( 'org_32656XXXXXX0438' // Your customer's organization ID ); // Share this link with your customer's IT admin via email or messaging // Example: '/magicLink/8930509d-68cf-4e2c-8c6d-94d2b5e2db43 console.log('Admin Portal URL:', portalLink.location); ``` Send this link to your customer’s IT administrator through email, Slack, or your preferred communication channel. They can configure their SSO connection without any developer involvement. * Embedded portal Embed the Admin Portal directly in your application using an iframe: embed-portal.js ```javascript // Generate a secure portal link at runtime const portalLink = await scalekit.organization.generatePortalLink(orgId); // Return the link to your frontend to embed in an iframe res.json({ portalUrl: portalLink.location }); ``` admin-settings.html ```html ``` Customers configure SSO without leaving your application, maintaining a consistent user experience. Learn more: [Embedded Admin Portal guide](/guides/admin-portal/#embed-the-admin-portal) **Enable domain verification** for seamless user experience. Once your customer verifies their domain (e.g., `@megacorp.org`), users can sign in without selecting their organization. Scalekit automatically routes them to the correct identity provider based on their email domain. **Pre-check SSO availability** before redirecting users. This prevents failed redirects when a user’s domain doesn’t have SSO configured: * Node.js check-sso-availability.js ```javascript 1 // Extract domain from user's email address 2 const domain = email.split('@')[1].toLowerCase(); // e.g., "megacorp.org" 3 4 // Check if domain has an active SSO connection 5 const connections = await scalekit.connections.listConnectionsByDomain({ 6 domain 7 }); 8 9 if (connections.length > 0) { 10 // Domain has SSO configured - redirect to identity provider 11 const authUrl = scalekit.getAuthorizationUrl(redirectUri, { 12 domainHint: domain // Automatically routes to correct IdP 13 }); 14 return res.redirect(authUrl); 15 } else { 16 // No SSO for this domain - show alternative login methods 17 return showPasswordlessLogin(); 18 } ``` * Python check\_sso\_availability.py ```python 1 # Extract domain from user's email address 2 domain = email.split('@')[1].lower() # e.g., "megacorp.org" 3 4 # Check if domain has an active SSO connection 5 connections = scalekit_client.connections.list_connections_by_domain( 6 domain=domain 7 ) 8 9 if len(connections) > 0: 10 # Domain has SSO configured - redirect to identity provider 11 options = AuthorizationUrlOptions() 12 options.domain_hint = domain # Automatically routes to correct IdP 13 14 auth_url = scalekit_client.get_authorization_url( 15 redirect_uri=redirect_uri, 16 options=options 17 ) 18 return redirect(auth_url) 19 else: 20 # No SSO for this domain - show alternative login methods 21 return show_passwordless_login() ``` * Go check\_sso\_availability.go ```go 1 // Extract domain from user's email address 2 parts := strings.Split(email, "@") 3 domain := strings.ToLower(parts[1]) // e.g., "megacorp.org" 4 5 // Check if domain has an active SSO connection 6 connections, err := scalekitClient.Connections.ListConnectionsByDomain(domain) 7 if err != nil { 8 // Handle error 9 return err 10 } 11 12 if len(connections) > 0 { 13 // Domain has SSO configured - redirect to identity provider 14 options := scalekit.AuthorizationUrlOptions{ 15 DomainHint: domain, // Automatically routes to correct IdP 16 } 17 18 authUrl, err := scalekitClient.GetAuthorizationUrl(redirectUri, options) 19 if err != nil { 20 return err 21 } 22 23 c.Redirect(http.StatusFound, authUrl.String()) 24 } else { 25 // No SSO for this domain - show alternative login methods 26 return showPasswordlessLogin() 27 } ``` * Java CheckSsoAvailability.java ```java 1 // Extract domain from user's email address 2 String[] parts = email.split("@"); 3 String domain = parts[1].toLowerCase(); // e.g., "megacorp.org" 4 5 // Check if domain has an active SSO connection 6 List connections = scalekitClient 7 .connections() 8 .listConnectionsByDomain(domain); 9 10 if (connections.size() > 0) { 11 // Domain has SSO configured - redirect to identity provider 12 AuthorizationUrlOptions options = new AuthorizationUrlOptions(); 13 options.setDomainHint(domain); // Automatically routes to correct IdP 14 15 String authUrl = scalekitClient 16 .authentication() 17 .getAuthorizationUrl(redirectUri, options) 18 .toString(); 19 20 return new RedirectView(authUrl); 21 } else { 22 // No SSO for this domain - show alternative login methods 23 return showPasswordlessLogin(); 24 } ``` This check ensures users only see SSO options when available, improving the login experience and reducing confusion.
---
# DOCUMENT BOUNDARY
---
# Add users to organizations
> Ways in which users join or get added to organizations
The journey of a user into your application begins with how they join an organization. A smooth onboarding experience sets the tone for their entire interaction with your product, while administrators need flexible options to manage their organization members. Scalekit supports a variety of ways for users to join organizations. This guide covers methods ranging from manual additions in the dashboard to fully automated provisioning. ## Enable user invitations through your app [Section titled “Enable user invitations through your app”](#enable-user-invitations-through-your-app) Scalekit lets you add user invitation features to your app, allowing users to invite others to join their organization. 1. #### Begin the invite flow [Section titled “Begin the invite flow”](#begin-the-invite-flow) When a user clicks the invite button in your application, retrieve the `organization_id` from their ID token or the application’s context. Then, call the Scalekit SDK with the invitee’s email address to send the invitation. * Node.js Express.js invitation API ```javascript 1 // POST /api/organizations/:orgId/invite 2 app.post('/api/organizations/:orgId/invite', async (req, res) => { 3 const { orgId } = req.params 4 const { email } = req.body 5 6 try { 7 // Create user and add to organization with invitation 8 const { user } = await scalekit.user.createUserAndMembership(orgId, { 9 email, 10 sendInvitationEmail: true, // Scalekit sends the invitation email 11 }) 12 13 res.json({ 14 message: 'Invitation sent successfully', 15 userId: user.id, 16 email: user.email 17 }) 18 } catch (error) { 19 res.status(400).json({ error: error.message }) 20 } 21 }) ``` * Python Django invitation API ```python 1 # Python - Django invitation API 2 @api_view(['POST']) 3 def invite_user_to_organization(request, org_id): 4 email = request.data.get('email') 5 6 try: 7 # Create user and add to organization with invitation 8 user_response = scalekit_client.user.create_user_and_membership(org_id, { 9 'email': email, 10 'send_invitation_email': True, # Scalekit sends the invitation email 11 }) 12 13 return JsonResponse({ 14 'message': 'Invitation sent successfully', 15 'user_id': user_response['user']['id'], 16 'email': user_response['user']['email'] 17 }) 18 except Exception as error: 19 return JsonResponse({'error': str(error)}, status=400) ``` * Go Gin invitation API ```go 1 // Go - Gin invitation API 2 func inviteUserToOrganization(c *gin.Context) { 3 orgID := c.Param("orgId") 4 5 var req struct { 6 Email string `json:"email"` 7 } 8 9 if err := c.ShouldBindJSON(&req); err != nil { 10 c.JSON(400, gin.H{"error": err.Error()}) 11 return 12 } 13 14 // Create user and add to organization with invitation 15 userResp, err := scalekitClient.User.CreateUserAndMembership(ctx, orgID, scalekit.CreateUserAndMembershipRequest{ 16 Email: req.Email, 17 SendInvitationEmail: scalekit.Bool(true), // Scalekit sends the invitation email 18 }) 19 20 if err != nil { 21 c.JSON(400, gin.H{"error": err.Error()}) 22 return 23 } 24 25 c.JSON(200, gin.H{ 26 "message": "Invitation sent successfully", 27 "user_id": userResp.User.Id, 28 "email": userResp.User.Email, 29 }) 30 } ``` * Java Spring Boot invitation API ```java 1 // Java - Spring Boot invitation API 2 @PostMapping("/api/organizations/{orgId}/invite") 3 public ResponseEntity