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 * Recommended Terminal
```bash
npx @scalekit-inc/cli setup
```
* Global install (for repeated use) Terminal
```bash
npm install -g @scalekit-inc/cli
scalekit setup
```
## 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.1.3"
```
```xml
com.scalekit
scalekit-sdk-java
2.1.3
```
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 * Recommended Terminal
```bash
npx @scalekit-inc/cli setup
```
* Global install (for repeated use) Terminal
```bash
npm install -g @scalekit-inc/cli
scalekit setup
```
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.1.3"
```
```xml
com.scalekit
scalekit-sdk-java
2.1.3
```
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.connection.listConnectionsByDomain(domain);
6
7
if (connections.length > 0) {
8
// Domain has SSO configured - redirect to identity provider
9
const authUrl = scalekit.getAuthorizationUrl(redirectUri, {
10
domainHint: domain // Automatically routes to correct IdP
11
});
12
return res.redirect(authUrl);
13
} else {
14
// No SSO for this domain - show alternative login methods
15
return showPasswordlessLogin();
16
}
```
* 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.connection.list_connections_by_domain(domain=domain).connections
6
7
if len(connections) > 0:
8
# Domain has SSO configured - redirect to identity provider
9
options = AuthorizationUrlOptions()
10
options.domain_hint = domain # Automatically routes to correct IdP
11
12
auth_url = scalekit_client.get_authorization_url(
13
redirect_uri=redirect_uri,
14
options=options
15
)
16
return redirect(auth_url)
17
else:
18
# No SSO for this domain - show alternative login methods
19
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