How OpenWork Integrates with Microsoft Entra ID for SSO and SCIM Provisioning

OpenWork integrates with Microsoft Entra ID through the Den API by detecting Entra-managed domains via issuer URL patterns, registering OIDC SSO connections with Better-Auth, and synchronizing user provisioning through SCIM tokens validated via Bearer token authentication.

The different-ai/openwork repository implements enterprise identity management through the Den API service located in ee/apps/den-api. This TypeScript-based backend handles OpenWork Microsoft Entra ID SSO SCIM integration by combining OIDC-based authentication with automated user lifecycle management via the SCIM 2.0 protocol.

Detecting Microsoft Entra ID Tenants

Domain Verification via Issuer Patterns

When an organization configures SSO, the system first determines whether the identity provider is Microsoft Entra ID. The function isMicrosoftEntraManagedDomain in ee/apps/den-api/src/sso-entra-domain.ts inspects the issuer URL for two specific patterns:

  • Host matches sts.windows.net or login.microsoftonline.com
  • Path contains a valid GUID representing the Entra tenant ID

If both conditions pass, the domain is flagged as Entra-managed and the domainVerified boolean is stored on the SsoProvider record. This flag enables Entra-specific UI hints and policy defaults throughout the authentication flow.

// Detect an Entra-managed domain
import { isMicrosoftEntraManagedDomain } from "./sso-entra-domain.js";

const isEntra = isMicrosoftEntraManagedDomain({
  domain: "contoso.com",
  issuer: "https://login.microsoftonline.com/12345678-90ab-cdef-1234-567890abcdef",
});
console.log(isEntra); // true

Registering SSO Providers for Entra-Managed Domains

Provider Registration with Better-Auth

The registerOrganizationSsoConnection function in ee/apps/den-api/src/sso.ts handles the creation of SSO connections. For Entra ID integrations, it constructs a unique provider ID using the format openwork-sso-<orgId> and registers the provider with Better-Auth. The connection details are persisted to SsoConnectionTable, with the domainVerified column set to true for Entra-managed domains.

// Register an OIDC (Entra) SSO connection
import { registerOrganizationSsoConnection } from "./sso.js";

await registerOrganizationSsoConnection({
  kind: "oidc",
  issuer: "https://login.microsoftonline.com/12345678-90ab-cdef-1234-567890abcdef",
  domain: "contoso.com",
  clientId: "YOUR-ENTRA-CLIENT-ID",
  clientSecret: "YOUR-ENTRA-CLIENT-SECRET",
  organizationId: "openwork-org-01",
  organizationSlug: "contoso",
  headers: new Headers(),
});

OIDC Discovery and Endpoint Resolution

After registration, the system performs OIDC discovery through resolveOidcEndpoints to obtain the authorization and token endpoints from the Entra tenant's metadata. The login URL redirects users to Microsoft's hosted authorization endpoint, using the tenant-specific issuer URL and the client credentials supplied by the organization administrator.

SCIM Provisioning Architecture

Token Generation and Storage

Once SSO is active, administrators enable automated user provisioning via rotateOrganizationScimToken in ee/apps/den-api/src/scim.ts. This function generates a bearer token formatted as openwork-scim-<orgId> and stores it in ScimProviderTable. The token uses a hashed storage strategy defined in ee/apps/den-api/src/scim-token-storage.ts to ensure secure persistence.

// Generate a SCIM token for Entra
import { rotateOrganizationScimToken } from "./scim.js";

const { scimToken } = await rotateOrganizationScimToken({
  organizationId: "openwork-org-01",
  headers: new Headers(),
});
// Feed `scimToken` into Microsoft Entra's SCIM provisioning UI

Validating Incoming SCIM Requests

When Microsoft Entra pushes user or group changes, the Den API validates the Bearer token through resolveScimProviderFromBearerToken. This resolver maps the token to the correct organization and SCIM provider configuration before processing the provisioning request.

Synchronizing External Identities

The syncExternalIdentityForProvider function processes create and update operations from Entra ID. It creates or updates ExternalIdentity records, marking them as SCIM + SSO when the identity already has an associated ssoProviderId, or pure SCIM otherwise. The sync also clears any "removed-member" memory flags, allowing subsequent JIT (Just-In-Time) SSO logins to re-activate previously de-provisioned users.

// Example request body sent by Entra (user creation)
{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "userName": "jane.doe@contoso.com",
  "name": { "givenName": "Jane", "familyName": "Doe" },
  "emails": [{ "value": "jane.doe@contoso.com", "primary": true }],
  "active": true
}

User Lifecycle and De-Provisioning

Handling SCIM Delete Operations

When Entra ID removes a user, the SCIM delete_user action triggers deleteScimProvisionedAccessForProvider in ee/apps/den-api/src/scim.ts. This function performs three critical cleanup steps:

  1. Deletes the associated AuthAccount record, revoking authentication capabilities
  2. Marks the ExternalIdentity as inactive to maintain audit history
  3. Optionally removes the global user record if no other organization memberships remain

This ensures that access revocation in Entra ID immediately propagates to OpenWork without requiring manual administrator intervention.

Summary

  • Domain Detection: OpenWork identifies Microsoft Entra ID tenants by parsing issuer URLs in ee/apps/den-api/src/sso-entra-domain.ts using the isMicrosoftEntraManagedDomain function.
  • SSO Registration: The registerOrganizationSsoConnection function in ee/apps/den-api/src/sso.ts creates OIDC providers with Better-Auth, storing connections in SsoConnectionTable with domainVerified flags.
  • SCIM Token Management: Bearer tokens generated via rotateOrganizationScimToken and validated through resolveScimProviderFromBearerToken secure the provisioning channel between Entra ID and OpenWork.
  • Lifecycle Sync: The syncExternalIdentityForProvider and deleteScimProvisionedAccessForProvider functions in ee/apps/den-api/src/scim.ts handle user creation, updates, and de-provisioning while maintaining referential integrity with ExternalIdentity records.

Frequently Asked Questions

How does OpenWork verify that a domain uses Microsoft Entra ID?

OpenWork checks the issuer URL provided during SSO configuration. If the host is sts.windows.net or login.microsoftonline.com and the path contains a valid tenant GUID, the isMicrosoftEntraManagedDomain function in ee/apps/den-api/src/sso-entra-domain.ts returns true. This sets the domainVerified flag on the SSO provider record, enabling Entra-specific authentication flows.

What is the difference between SSO and SCIM in OpenWork's Entra integration?

SSO (Single Sign-On) handles authentication via OIDC, allowing users to log in through Microsoft Entra ID's authorization endpoints. SCIM (System for Cross-domain Identity Management) handles automated provisioning, synchronizing user attributes and group memberships from Entra ID to OpenWork via the syncExternalIdentityForProvider function. While SSO can function independently, SCIM requires a valid SSO connection and uses bearer tokens generated by rotateOrganizationScimToken.

How are SCIM tokens secured in the OpenWork architecture?

SCIM tokens follow the format openwork-scim-<orgId> and are stored using a hashed strategy implemented in ee/apps/den-api/src/scim-token-storage.ts. When Entra ID sends provisioning requests, the resolveScimProviderFromBearerToken function in ee/apps/den-api/src/scim.ts validates the bearer token against the hashed value in ScimProviderTable before processing any user data changes.

What happens when a user is removed from Microsoft Entra ID?

The SCIM protocol sends a delete request to OpenWork's SCIM endpoint, triggering deleteScimProvisionedAccessForProvider. This function deletes the user's AuthAccount, marks the ExternalIdentity as inactive, and conditionally removes the global user if no other organization memberships exist. This ensures immediate access revocation while preserving audit trails.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →