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

> Learn how OpenWork integrates with Microsoft Entra ID for seamless SSO and SCIM provisioning. Automate user management and enhance security with this powerful integration.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-08-09

---

**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`](https://github.com/different-ai/openwork/blob/main/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.

```typescript
// 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`](https://github.com/different-ai/openwork/blob/main/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.

```typescript
// 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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/scim-token-storage.ts) to ensure secure persistence.

```typescript
// 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.

```json
// 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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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.