SSO & SCIM
Enterprise Single Sign-On (SSO) and automated user provisioning (SCIM) for centralized identity management.
SSO Overview
Connect Deeployd to your identity provider:
User → Identity Provider → SAML/OIDC → Deeployd
Supported Providers
| Provider | SAML | OIDC |
|---|---|---|
| Okta | Yes | Yes |
| Azure AD | Yes | Yes |
| Google Workspace | Yes | Yes |
| OneLogin | Yes | Yes |
| Ping Identity | Yes | Yes |
| Auth0 | Yes | Yes |
| JumpCloud | Yes | Yes |
| Custom SAML IdP | Yes | - |
| Custom OIDC IdP | - | Yes |
Setting Up SSO
SAML Configuration
- Create SSO Connection
const connection = await client.sso.create({
name: 'Corporate Okta',
providerType: 'saml',
wellKnownProvider: 'okta', // Optional: for guided setup
samlConfig: {
// From your IdP
idpEntityId: 'http://www.okta.com/exk123',
idpSsoUrl: 'https://yourcompany.okta.com/app/123/sso/saml',
idpSloUrl: 'https://yourcompany.okta.com/app/123/slo/saml', // Optional
idpCertificate: '-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----',
// Attribute mapping
attributeMapping: {
email: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
firstName: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname',
lastName: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname',
groups: 'http://schemas.xmlsoap.org/claims/Group'
}
},
// Settings
allowedDomains: ['company.com', 'subsidiary.com'],
enforceForDomains: true, // Require SSO for these domains
jitProvisioning: true, // Create users on first login
defaultRole: 'member'
});
// Get SP metadata for your IdP
console.log(connection.spEntityId);
console.log(connection.spAcsUrl);
console.log(connection.spMetadataUrl);
- Configure Your IdP
Use the Service Provider (SP) details from Deeployd:
| Setting | Value |
|---|---|
| SP Entity ID | https://api.deeployd.com/sso/saml/{connection-id} |
| ACS URL | https://api.deeployd.com/sso/saml/{connection-id}/acs |
| SP Metadata | https://api.deeployd.com/sso/saml/{connection-id}/metadata |
- Test the Connection
const result = await client.sso.test(connection.id);
if (result.success) {
console.log('SSO connection working!');
} else {
console.log('Error:', result.error);
}
OIDC Configuration
const connection = await client.sso.create({
name: 'Corporate Azure AD',
providerType: 'oidc',
wellKnownProvider: 'azure-ad',
oidcConfig: {
// From Azure AD app registration
issuer: 'https://login.microsoftonline.com/{tenant-id}/v2.0',
authorizationEndpoint: 'https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize',
tokenEndpoint: 'https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token',
userInfoEndpoint: 'https://graph.microsoft.com/oidc/userinfo',
jwksUri: 'https://login.microsoftonline.com/{tenant-id}/discovery/v2.0/keys',
clientId: 'your-app-client-id',
clientSecret: 'your-app-client-secret',
scopes: ['openid', 'profile', 'email'],
claimMapping: {
email: 'email',
firstName: 'given_name',
lastName: 'family_name',
groups: 'groups'
}
},
allowedDomains: ['company.com'],
jitProvisioning: true
});
Role Mapping
Map IdP groups to Deeployd roles:
await client.sso.update(connection.id, {
roleMapping: [
{ idpGroup: 'Deeployd-Admins', role: 'admin' },
{ idpGroup: 'Deeployd-Users', role: 'member' },
{ idpGroup: 'Deeployd-Viewers', role: 'viewer' }
],
defaultRole: 'viewer' // If no group matches
});
SSO Login Flow
Initiate Login
// Redirect user to initiate SSO
const loginUrl = await client.sso.initiateLogin({
connectionId: 'sso-123',
redirectUri: 'https://your-app.com/auth/callback'
});
window.location.href = loginUrl;
Handle Callback
// In your callback handler
app.get('/auth/callback', async (req, res) => {
const { code, state } = req.query;
const tokens = await client.sso.handleCallback({
connectionId: 'sso-123',
code,
state
});
// Set session and redirect
req.session.accessToken = tokens.accessToken;
res.redirect('/dashboard');
});
SCIM Overview
System for Cross-domain Identity Management (SCIM) enables automatic user provisioning:
Identity Provider → SCIM API → Deeployd
Benefits
- Automated provisioning: Users created when added in IdP
- Automated deprovisioning: Users removed when deleted in IdP
- Sync groups: Team membership from IdP groups
- Real-time updates: Changes sync immediately
Setting Up SCIM
Enable SCIM
const scimConfig = await client.scim.enable();
console.log(scimConfig.baseUrl); // SCIM endpoint URL
console.log(scimConfig.token); // Bearer token for authentication
SCIM Endpoints
| Endpoint | Method | Description |
|---|---|---|
/scim/v2/Users | GET | List users |
/scim/v2/Users | POST | Create user |
/scim/v2/Users/{id} | GET | Get user |
/scim/v2/Users/{id} | PATCH | Update user |
/scim/v2/Users/{id} | DELETE | Delete user |
/scim/v2/Groups | GET | List groups |
/scim/v2/Groups | POST | Create group |
/scim/v2/Groups/{id} | PATCH | Update group |
/scim/v2/Groups/{id} | DELETE | Delete group |
Configure Your IdP
In Okta, Azure AD, or other SCIM-capable IdP:
| Setting | Value |
|---|---|
| SCIM Base URL | https://api.deeployd.com/scim/v2 |
| Authentication | Bearer Token |
| Token | {your-scim-token} |
| Provisioning Features | Create, Update, Delete |
SCIM User Schema
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "jdoe@company.com",
"name": {
"givenName": "John",
"familyName": "Doe"
},
"emails": [
{
"value": "jdoe@company.com",
"primary": true
}
],
"active": true,
"groups": [
{
"value": "group-123",
"display": "Engineering"
}
]
}
SCIM Group Schema
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"displayName": "Engineering",
"members": [
{
"value": "user-123",
"display": "John Doe"
}
]
}
SCIM Logs
Monitor SCIM operations:
const logs = await client.scim.getLogs({
startDate: '2024-01-01',
operation: 'create', // create, update, delete
status: 'success' // success, error
});
for (const log of logs.data) {
console.log({
timestamp: log.timestamp,
operation: log.operation,
resourceType: log.resourceType,
status: log.status,
request: log.requestBody,
response: log.responseBody
});
}
Rotate SCIM Token
// Generate new token
const newToken = await client.scim.rotateToken();
// Update your IdP with the new token
console.log(newToken.token);
// Old token remains valid for 24 hours to allow migration
Domain Verification
Verify domain ownership for SSO:
// Start verification
const verification = await client.sso.verifyDomain('company.com');
// Add DNS record
console.log(verification.recordType); // TXT
console.log(verification.recordName); // _deeployd-verification
console.log(verification.recordValue); // deeployd-verify=xxxxx
// Check verification status
const status = await client.sso.checkDomainVerification('company.com');
console.log(status.verified); // true/false
SSO Enforcement
Require SSO for specific domains:
await client.sso.update(connection.id, {
enforceForDomains: true,
allowedDomains: ['company.com', 'subsidiary.com']
});
// Users with these email domains must use SSO
// Password login will be disabled for them
Troubleshooting
"SAML Response Invalid"
- Check IdP certificate is correct
- Verify ACS URL matches
- Check system clocks are in sync (SAML has time constraints)
"User Not Provisioned"
- Check JIT provisioning is enabled
- Verify email domain is in allowed list
- Check attribute mapping for email claim
"Group Sync Not Working"
- Verify groups claim is in SAML/OIDC response
- Check role mapping configuration
- Ensure IdP is sending group memberships
SCIM Errors
// Check SCIM logs for details
const errors = await client.scim.getLogs({
status: 'error',
limit: 10
});
for (const error of errors.data) {
console.log(error.errorMessage);
console.log(error.requestBody);
}
Best Practices
1. Test Before Enforcement
// Start with SSO optional
enforceForDomains: false
// Test with pilot users
// Then enable enforcement
enforceForDomains: true
2. Keep Backup Admin
Always have at least one admin who can login without SSO (for emergencies).
3. Map Groups Carefully
// Be explicit about role mappings
roleMapping: [
{ idpGroup: 'Deeployd-Admins', role: 'admin' },
{ idpGroup: 'Deeployd-Users', role: 'member' }
],
defaultRole: 'viewer' // Safe default
4. Monitor SCIM Sync
// Set up alerts for SCIM failures
await client.webhooks.create({
url: 'https://your-app.com/alerts',
events: ['scim.sync_failed']
});
Next: Learn about Approvals for human oversight of sensitive operations.