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.netorlogin.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:
- Deletes the associated
AuthAccountrecord, revoking authentication capabilities - Marks the
ExternalIdentityas inactive to maintain audit history - 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.tsusing theisMicrosoftEntraManagedDomainfunction. - SSO Registration: The
registerOrganizationSsoConnectionfunction inee/apps/den-api/src/sso.tscreates OIDC providers with Better-Auth, storing connections inSsoConnectionTablewithdomainVerifiedflags. - SCIM Token Management: Bearer tokens generated via
rotateOrganizationScimTokenand validated throughresolveScimProviderFromBearerTokensecure the provisioning channel between Entra ID and OpenWork. - Lifecycle Sync: The
syncExternalIdentityForProvideranddeleteScimProvisionedAccessForProviderfunctions inee/apps/den-api/src/scim.tshandle user creation, updates, and de-provisioning while maintaining referential integrity withExternalIdentityrecords.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →