Single Sign-On (SSO) & SCIM Provisioning
Contract Lucidity supports enterprise Single Sign-On via OpenID Connect (OIDC) and automated user/group provisioning via SCIM 2.0. This lets your users authenticate through your organization's identity provider (Azure AD / Entra ID, Google Workspace, Okta, etc.) and automatically keeps user accounts and group memberships in sync.
How SSO Works
What the Identity Provider Handles
Contract Lucidity delegates authentication to your IdP. This means:
- Multi-Factor Authentication (MFA) -- configured in your IdP, not in CL
- Conditional access policies -- device compliance, location restrictions, etc.
- Password policies -- complexity, rotation, lockout
- Session management -- the IdP controls the authentication experience
CL only receives the claims from the ID token (email, name, subject ID) after successful authentication.
Prerequisites
Before configuring SSO, ensure:
- You have admin access to both Contract Lucidity and your identity provider
- Your CL instance is accessible via HTTPS (required for secure OIDC redirects)
- You know your CL deployment's public URL (e.g.,
https://contracts.yourcompany.com) FRONTEND_URLis set correctly on the backend service (see below)
The backend uses the FRONTEND_URL environment variable to construct SSO redirect URIs and post-login callbacks. If this is set incorrectly (e.g., http://localhost:3000 instead of your actual domain), SSO will fail with redirect URI mismatch errors from your identity provider.
Set this on the cl-backend service environment:
FRONTEND_URL=https://contracts.yourcompany.com
This must exactly match the domain users access in their browser, including the scheme (https://). Do not include a trailing slash.
SSO Configuration in Contract Lucidity
Navigate to Settings > Security > SSO in the admin panel. The configuration fields are:
| Field | Description | Example |
|---|---|---|
| Provider Name | Display name shown on the login button | Azure AD, Okta, Google |
| Issuer URL | OIDC issuer URL (must serve /.well-known/openid-configuration) | See provider sections below |
| Client ID | Application/client ID from your IdP | a1b2c3d4-... |
| Client Secret | Client secret from your IdP (stored encrypted) | secret~... |
| Scopes | OIDC scopes to request | openid email profile (default) |
| Auto-create users | Create CL accounts for new SSO users on first login | true (default) |
| Default role | Role assigned to auto-created users | user (default) |
| Default group | Group auto-created users are added to (optional) | Select from existing groups |
| Enabled | Toggle SSO on/off | false (default) |
The Client Secret is encrypted at rest using AES (Fernet) derived from your JWT_SECRET_KEY. It is never returned in API responses -- only a redacted version (last 4 characters) is displayed.
Redirect URI
The callback URI is dynamically generated based on your deployment's domain:
https://your-domain.com/api/auth/sso/callback
You must register this exact URI in your identity provider's application configuration. CL uses the request's origin to build the redirect URI, so it works correctly regardless of the domain name.
SCIM 2.0 Provisioning
SCIM (System for Cross-domain Identity Management) allows your identity provider to automatically push user and group changes to Contract Lucidity. Instead of manually creating users or relying solely on just-in-time provisioning at login, SCIM keeps your CL user directory in continuous sync with your IdP.
What SCIM Does
| Action in IdP | Result in Contract Lucidity |
|---|---|
| Assign a user to the CL app | User account is created (or linked if email matches an existing account) |
| Update a user's name or email | User profile is updated in CL |
| Unassign / deactivate a user | User is deactivated in CL (soft-delete; data is preserved) |
| Create / push a group | Group is created in CL with a description noting it was provisioned via SCIM |
| Add/remove group members | Group membership is synced in CL |
| Delete a group | Group is removed from CL |
How SCIM Authentication Works
SCIM uses a long-lived bearer token for authentication. The token is generated in CL, shown once to the admin, and stored as a bcrypt hash. Each SCIM request from the IdP includes this token in the Authorization: Bearer <token> header.
Setting Up SCIM Provisioning
Step 1: Configure SSO First
SCIM provisioning requires an active SSO configuration. Complete the SSO setup (described above) before enabling SCIM.
Step 2: Generate a SCIM Token
- Navigate to Settings > Security > SSO
- Scroll to the SCIM Provisioning section
- Click Generate SCIM Token
- Copy the token immediately -- it is displayed only once and cannot be retrieved later
- Store it securely (you will paste it into your IdP's SCIM configuration)
The SCIM token is shown only once. If you lose it, you must revoke the current token and generate a new one, then update the token in your identity provider's configuration.
Step 3: Note the SCIM Endpoint URL
The SCIM base URL for your deployment is:
https://your-backend-domain.com/scim/v2
The SCIM endpoint is served at /scim/v2 on the backend service, not under the /api/ prefix. The default frontend proxy in Contract Lucidity only forwards /api/* requests to the backend.
This means your identity provider must be able to reach the backend's SCIM endpoint directly. You have two options:
- Add a frontend rewrite rule for
/scim/*in yournext.config.ts(recommended for simplicity -- keeps a single public URL) - Expose the backend directly on a separate URL or port for SCIM traffic only
See the Configuration Reference for details on setting up the SCIM route.
Step 4: Configure Your Identity Provider
Follow the provider-specific instructions below to point your IdP at the CL SCIM endpoint.
Step 5: Test the Integration
After configuring SCIM in your IdP:
- Assign a test user to the CL application in your IdP
- Verify the user appears in Settings > Users in CL
- Try updating the user's name in the IdP and confirm it syncs
- Try assigning a group and verify it appears in Settings > Groups
Revoking a SCIM Token
To revoke the current SCIM token:
- Navigate to Settings > Security > SSO
- In the SCIM Provisioning section, click Revoke Token
- The IdP will receive
401 Unauthorizederrors on subsequent SCIM requests until a new token is generated and configured
SCIM Capabilities
Contract Lucidity's SCIM implementation supports:
- Users: Create, read, update (PUT and PATCH), deactivate (soft-delete), list with filtering
- Groups: Create, read, update (PUT and PATCH including member add/remove), delete, list with filtering
- Filtering:
userName eq "value",externalId eq "value",displayName eq "value" - Pagination: Standard SCIM 1-based pagination with
startIndexandcountparameters (max 200 per page) - Discovery:
/ServiceProviderConfig,/Schemas,/ResourceTypesendpoints
Not supported: bulk operations, password changes, sorting, ETags.
Provider Setup: Azure AD / Entra ID
Cloud providers frequently update their admin interfaces and features. The steps below were verified against Microsoft's documentation as of early 2026. If the steps do not match what you see, consult the official Microsoft documentation linked below.
Official docs: Develop a SCIM endpoint for user provisioning from Microsoft Entra ID | What is automated app user provisioning
SSO Setup (OIDC)
1. Register an Application
- Go to the Azure Portal > Microsoft Entra ID > App registrations
- Click New registration
- Set:
- Name:
Contract Lucidity - Supported account types: Choose based on your needs (typically "Accounts in this organizational directory only")
- Redirect URI: Select Web and enter
https://your-domain.com/api/auth/sso/callback
- Name:
- Click Register
2. Get Credentials
- From the app registration overview, copy the Application (client) ID
- Go to Certificates & secrets > New client secret
- Copy the secret Value (not the Secret ID)
3. Find the Issuer URL
The issuer URL for Azure AD is:
https://login.microsoftonline.com/{tenant-id}/v2.0
Find your tenant ID on the Overview page of the app registration (Directory (tenant) ID).
4. Configure in CL
| Field | Value |
|---|---|
| Provider Name | Azure AD |
| Issuer URL | https://login.microsoftonline.com/{tenant-id}/v2.0 |
| Client ID | Application (client) ID from step 2 |
| Client Secret | Client secret value from step 2 |
| Scopes | openid email profile |
Azure AD does not include name or email claims in the ID token by default. You must add them manually:
- Go to Token configuration > Add optional claim > select ID token type
- Add email, given_name, and family_name
- If prompted to enable Microsoft Graph permissions, accept
Without email, CL cannot identify the user. Without given_name and family_name, user accounts will be created with a blank name (the email prefix is used as a fallback for first name).
SCIM Provisioning Setup (Azure AD / Entra ID)
Azure Entra ID has built-in SCIM provisioning support for both gallery and non-gallery (custom) applications.
1. Create an Enterprise Application for Provisioning
Since Contract Lucidity is not in the Microsoft app gallery, you create a non-gallery application:
- In the Azure Portal, go to Microsoft Entra ID > Enterprise applications
- Click New application > Create your own application
- Enter a name (e.g.,
Contract Lucidity - Provisioning) and select Integrate any other application you don't find in the gallery (Non-gallery) - Click Create
You will have two separate app registrations in Entra ID: one for SSO (OIDC) and one for provisioning (SCIM). This is normal for non-gallery applications.
2. Configure Provisioning
- Open the enterprise application you just created
- Go to the Provisioning blade in the left menu
- Click Get started, then set Provisioning Mode to Automatic
- Under Admin Credentials:
- Tenant URL: Enter your SCIM endpoint URL:
https://your-backend-domain.com/scim/v2 - Secret Token: Paste the SCIM bearer token you generated in CL
- Tenant URL: Enter your SCIM endpoint URL:
- Click Test Connection to verify Entra ID can reach the SCIM endpoint
- Click Save
3. Configure Attribute Mappings
After saving the admin credentials:
- Expand the Mappings section
- Click Provision Microsoft Entra ID Users
- Review the default attribute mappings. The key mappings for CL are:
| Azure AD Attribute | SCIM Attribute | Notes |
|---|---|---|
userPrincipalName | userName | CL uses this as the email address |
givenName | name.givenName | First name |
surname | name.familyName | Last name |
mail | emails[type eq "work"].value | Alternative email mapping |
Switch([IsSoftDeleted]...) | active | Controls user active status |
objectId | externalId | Links the Entra ID user to the CL user |
- Click Provision Microsoft Entra ID Groups to configure group provisioning
- Ensure
displayNamemaps todisplayNameandmembersmaps tomembers
If your users sign in with their email address (not UPN), change the userName mapping source from userPrincipalName to mail. CL uses userName as the email address for user lookup.
4. Assign Users and Groups
- Go to the Users and groups blade of the enterprise application
- Click Add user/group
- Select the users or groups you want to provision to CL
- Click Assign
5. Start Provisioning
- Return to the Provisioning blade
- Click Start provisioning
- Entra ID will perform an initial sync cycle (which may take several minutes to hours depending on the number of users)
- Subsequent incremental syncs run approximately every 40 minutes
Provider Setup: Google Workspace
Cloud providers frequently update their admin interfaces and features. The steps below were verified against Google's documentation as of early 2026. If the steps do not match what you see, consult the official Google documentation linked below.
Official docs: About automated user provisioning | Set up your own custom SAML app
SSO Setup (OIDC)
1. Create OAuth Credentials
- Go to the Google Cloud Console > APIs & Services > Credentials
- Click Create Credentials > OAuth client ID
- Set:
- Application type: Web application
- Name:
Contract Lucidity - Authorized redirect URIs:
https://your-domain.com/api/auth/sso/callback
- Click Create
2. Get Credentials
Copy the Client ID and Client secret from the confirmation dialog.
3. Configure in CL
| Field | Value |
|---|---|
| Provider Name | Google |
| Issuer URL | https://accounts.google.com |
| Client ID | Client ID from step 2 |
| Client Secret | Client secret from step 2 |
| Scopes | openid email profile |
To restrict login to your Google Workspace domain, configure the allowed domains in Google Admin Console under Security > API Controls. CL does not filter by domain -- the IdP must enforce this.
SCIM Provisioning (Google Workspace)
Google Workspace's automated provisioning only supports applications in its pre-integrated app catalog. It does not support pushing users via SCIM to arbitrary custom applications like Contract Lucidity.
Workarounds:
- Use SSO with auto-create: Enable "Auto-create users" in CL's SSO settings. Users will be created in CL on their first SSO login (just-in-time provisioning).
- Use a third-party identity governance tool: Tools like Okta, Microsoft Entra ID, or dedicated SCIM bridges can sit between Google Workspace and CL to provide SCIM provisioning.
- Manual provisioning: Create user accounts manually in CL or use the CL admin API.
If your organization uses Google Workspace as its primary IdP, just-in-time provisioning via SSO (with auto-create enabled) is the recommended approach. Users are created automatically on first login, and deactivation can be managed through CL's admin UI.
Provider Setup: Okta
Cloud providers frequently update their admin interfaces and features. The steps below were verified against Okta's documentation as of early 2026. If the steps do not match what you see, consult the official Okta documentation linked below.
Official docs: Add a private SCIM integration | Add SCIM provisioning to app integrations | Understanding SCIM
SSO Setup (OIDC)
1. Create an Application
- Go to your Okta admin console > Applications > Create App Integration
- Select OIDC - OpenID Connect and Web Application
- Set:
- App integration name:
Contract Lucidity - Sign-in redirect URIs:
https://your-domain.com/api/auth/sso/callback - Sign-out redirect URIs: (optional, leave blank)
- Assignments: Choose who can access the app
- App integration name:
- Click Save
2. Get Credentials
From the application's General tab, copy the Client ID and Client secret.
3. Find the Issuer URL
The issuer URL for Okta is:
https://{your-okta-domain}/oauth2/default
Or if you use a custom authorization server:
https://{your-okta-domain}/oauth2/{authorization-server-id}
4. Configure in CL
| Field | Value |
|---|---|
| Provider Name | Okta |
| Issuer URL | https://{your-okta-domain}/oauth2/default |
| Client ID | Client ID from step 2 |
| Client Secret | Client secret from step 2 |
| Scopes | openid email profile |
SCIM Provisioning Setup (Okta)
Okta supports SCIM provisioning for custom applications through its App Integration Wizard.
1. Enable SCIM on the Application
You can either add SCIM to your existing OIDC app or create a separate SCIM app:
Option A: Use the App Catalog SCIM template (recommended)
- Go to Applications > Browse App Catalog
- Search for SCIM 2.0 Test App (Header Auth) and click Add Integration
- Give it a name (e.g.,
Contract Lucidity - Provisioning) and click Done
Option B: Add SCIM to your existing OIDC app
- Open your Contract Lucidity OIDC application
- Go to the General tab and edit the Provisioning section
- Select SCIM as the provisioning type
2. Configure API Integration
- Click the Provisioning tab
- Click Configure API Integration
- Check Enable API Integration
- Set the fields:
- SCIM 2.0 Base URL:
https://your-backend-domain.com/scim/v2 - Unique identifier field for users:
userName - Supported provisioning actions: Check all that apply:
- Push New Users
- Push Profile Updates
- Push Groups
- Authentication Mode: Select HTTP Header
- Authorization / Bearer Token: Paste the SCIM token generated in CL
- SCIM 2.0 Base URL:
- Click Test API Credentials to verify connectivity
- Click Save
3. Configure Provisioning to App
- On the Provisioning tab, click To App in the left panel
- Click Edit and enable:
- Create Users: Checked
- Update User Attributes: Checked
- Deactivate Users: Checked
- Click Save
4. Configure Attribute Mappings
- Still on the Provisioning tab, scroll to the attribute mappings
- Verify that these mappings are configured:
| Okta Attribute | SCIM Attribute |
|---|---|
userName | userName |
givenName | name.givenName |
familyName | name.familyName |
email | emails[type eq "work"].value |
externalId | externalId |
- CL requires
userNameto be the user's email address. If your OktauserNameis not an email, mapemailtouserNameinstead.
5. Assign Users and Groups
- Go to the Assignments tab
- Assign individual users or groups to the application
- If pushing groups, go to the Push Groups tab and configure which Okta groups should be pushed to CL
Auto-Create Users
When Auto-create users is enabled (the default), Contract Lucidity will automatically create a new user account the first time someone logs in via SSO. The new account is configured with:
- Email: From the
emailclaim in the ID token - Name: From the
given_nameandfamily_nameclaims - Role: The configured Default role (typically
user) - Group: Added to the configured Default group (if set)
- Active:
true must_change_password:false(SSO users do not have passwords)
If auto-create is disabled, only users with pre-existing CL accounts (matching by email or SSO subject ID) can log in via SSO.
You can pre-create user accounts in CL with matching email addresses before enabling SSO. When those users log in via SSO for the first time, their existing account is linked to the SSO identity rather than creating a duplicate. The same linking behavior applies to SCIM-provisioned users -- if a SCIM push creates a user with an email that already exists in CL, the existing account is linked to the SSO identity.
SSO and Local Auth Coexistence
SSO and local password authentication run side-by-side:
- The login page shows a "Sign in with SSO" button when SSO is enabled
- Users can still log in with email and password if they have a local account
- An admin can disable SSO at any time without affecting local accounts
- SSO users who have been linked from a pre-existing local account retain their password and can use either method
CL supports one SSO provider at a time. The sso_config table stores a single configuration record.
Troubleshooting
Redirect URI Mismatch
Symptom: Identity provider returns an error like "redirect_uri does not match", "invalid redirect_uri", or Azure error AADSTS50011.
Cause: The redirect URI sent by CL does not match what is registered in your IdP. CL constructs the redirect URI from the FRONTEND_URL environment variable on the backend.
Fix:
- Check
FRONTEND_URLon the cl-backend service. This is the most common cause. It must be set to your public domain (e.g.,https://contracts.yourcompany.com), nothttp://localhost:3000or an internal hostname - Register the exact URI in your IdP:
https://your-domain.com/api/auth/sso/callback(whereyour-domain.commatchesFRONTEND_URL) - Ensure there is no trailing slash mismatch
- After changing
FRONTEND_URL, you must recreate the backend container (not just restart it) for the new env var to take effect:docker compose up -d cl-backend
Token Validation Failed
Symptom: Login redirects back with "ID token validation failed" error.
Possible causes:
- Clock skew: The server's clock is out of sync. Ensure NTP is configured.
- Issuer mismatch: The
issuer_urlin CL settings does not match theissclaim in the token. Check for trailing slashes. - Audience mismatch: The
client_idin CL settings does not match theaudclaim. Verify the client ID is correct.
User Not Found (Auto-Create Disabled)
Symptom: "User not found and automatic account creation is disabled" error.
Fix: Either enable auto-create in SSO settings, or pre-create the user account in CL with a matching email address.
Missing Email Claim
Symptom: "ID token does not contain an email claim" error.
Fix: Configure your IdP to include the email claim in the ID token:
- Azure AD: Add the email optional claim in Token configuration
- Google: The
emailscope includes this by default - Okta: Ensure the
emailscope is granted and the user has an email set
SSO State Expired
Symptom: "Invalid or expired SSO state parameter" error.
Cause: The OIDC state parameter (stored in Redis with a 5-minute TTL) has expired. This happens if the user takes too long to complete authentication at the IdP.
Fix: Have the user try again. If the issue persists, verify Redis is running and accessible.
SCIM Provisioning Not Working
Symptom: Users or groups are not being synced from the IdP to CL.
Possible causes and fixes:
- 401 Unauthorized: The SCIM token is invalid or has been revoked. Generate a new token in CL and update it in your IdP.
- Connection timeout: The IdP cannot reach the SCIM endpoint. Verify the SCIM URL is correct and that the backend is accessible from the internet (see the routing note in the SCIM setup section above).
- SCIM not configured: Ensure SSO is configured first, then generate a SCIM token. The SCIM endpoints return 401 if no SSO config with a SCIM token hash exists.
- User already exists (409): For groups, a 409 means a group with the same name already exists. Rename the group in CL or in the IdP to resolve the conflict.
- Attribute mapping issues: Verify that
userNamein the SCIM payload is the user's email address. CL usesuserNameas the email field.
SCIM Deactivation vs Deletion
Symptom: Deleting a user in the IdP does not remove them from CL.
Expected behavior: SCIM DELETE /Users performs a soft-delete (deactivates the user) rather than permanently removing the account. This preserves audit trails and document associations. The user can be reactivated later if needed.
API Reference
SSO Admin Endpoints (require admin auth)
| Method | Endpoint | Description |
|---|---|---|
GET | /api/auth/sso/config | Get current SSO configuration (secret redacted) |
POST | /api/auth/sso/config | Create or update SSO configuration |
DELETE | /api/auth/sso/config | Delete SSO configuration |
POST | /api/auth/sso/scim-token | Generate a SCIM bearer token (shown once) |
DELETE | /api/auth/sso/scim-token | Revoke the current SCIM token |
SSO Public Endpoints (no auth required)
| Method | Endpoint | Description |
|---|---|---|
GET | /api/auth/sso/status | Check if SSO is configured and enabled |
GET | /api/auth/sso/login | Initiate SSO login (redirects to IdP) |
GET | /api/auth/sso/callback | Handle IdP callback (not called directly) |
SCIM Endpoints (require SCIM bearer token)
| Method | Endpoint | Description |
|---|---|---|
GET | /scim/v2/ServiceProviderConfig | SCIM service provider capabilities |
GET | /scim/v2/Schemas | Supported SCIM schemas |
GET | /scim/v2/ResourceTypes | Supported resource types |
GET | /scim/v2/Users | List users (supports filtering and pagination) |
POST | /scim/v2/Users | Create or link a user |
GET | /scim/v2/Users/:id | Get a specific user |
PUT | /scim/v2/Users/:id | Replace a user |
PATCH | /scim/v2/Users/:id | Partially update a user |
DELETE | /scim/v2/Users/:id | Deactivate a user (soft-delete) |
GET | /scim/v2/Groups | List groups (supports filtering and pagination) |
POST | /scim/v2/Groups | Create a group |
GET | /scim/v2/Groups/:id | Get a specific group |
PUT | /scim/v2/Groups/:id | Replace a group (full member replacement) |
PATCH | /scim/v2/Groups/:id | Add/remove members or update group name |
DELETE | /scim/v2/Groups/:id | Delete a group |