How to Integrate OpenWork with Microsoft Entra ID for SSO and SCIM Provisioning

To integrate OpenWork with Microsoft Entra ID, configure SAML SSO in the OpenWork dashboard, exchange ACS and metadata URLs with your Entra enterprise application, then enable SCIM provisioning with a bearer token-generated endpoint.

OpenWork provides native support for Microsoft Entra ID integration through SAML-based single sign-on (SSO) and SCIM 2.0 user provisioning. This guide explains how to integrate OpenWork with Microsoft Entra ID based on the actual source code implementation in the different-ai/openwork repository.

Architecture Overview

OpenWork exposes dedicated endpoints for SSO and SCIM operations, with Better Auth handling the underlying protocol implementation.

Component OpenWork URL Purpose
SSO management /dashboard/sso, /v1/sso/saml Create and manage organization-scoped SSO connections
SAML callback /api/auth/sso/saml2/sp/acs/openwork-sso-<org-id> Consumes Entra's SAML response
SAML metadata /api/auth/sso/saml2/sp/metadata?providerId=openwork-sso-<org-id> Service provider metadata for Entra
SSO sign-in /sso/<org-slug> Entry point for SP-initiated login
SCIM management /dashboard/scim, /v1/scim/token Generate and rotate SCIM bearer tokens
SCIM provisioning /api/auth/scim/v2 Handles user create, update, delete, and group sync

The OpenWork layer adds organization-scoping, policy enforcement, and audit logging on top of Better Auth's protocol handling. Security features include signed assertion verification, issuer validation, timestamp checks, and rejection of deprecated algorithms.

How OpenWork Detects Microsoft Entra ID

OpenWork automatically recognizes Entra-managed domains through issuer URL pattern matching. The detection logic in [sso-entra-domain.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/sso-entra-domain.ts) validates the tenant ID format:

function getMicrosoftTenantId(value: string) {
  try {
    const url = new URL(value);
    const host = url.hostname.toLowerCase();
    if (host !== "sts.windows.net" && host !== "login.microsoftonline.com") return null;
    const tenantId = url.pathname.split("/").filter(Boolean)[0];
    return tenantId && /^[0-9a-f-]{36}$/i.test(tenantId) ? tenantId.toLowerCase() : null;
  } catch { return null; }
}

export function isMicrosoftEntraManagedDomain(input: { domain: string; issuer: string; entryPoint?: string | null }) {
  return getMicrosoftTenantId(input.issuer) !== null;
}

When you submit an IdP Issuer URL like https://sts.windows.net/<tenant-id>/, OpenWork calls isMicrosoftEntraManagedDomain. A true result enables automatic domain verification without DNS TXT validation for .onmicrosoft.com tenants.

SCIM Token Authentication

SCIM requests use bearer token authentication with a multi-part encoding scheme. The resolver in [scim.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/scim.ts) extracts the organization and provider context:

export async function resolveScimProviderFromBearerToken(bearerToken: string) {
  let decoded: string;
  try { decoded = decodeBase64Url(bearerToken); } catch { return null; }

  const [rawToken, providerId, ...organizationParts] = decoded.split(":");
  const organizationId = organizationParts.join(":");
  if (!rawToken || !providerId || !organizationId) return null;

  const providerRows = await db
    .select()
    .from(ScimProviderTable)
    .where(and(eq(ScimProviderTable.providerId, providerId),
               eq(ScimProviderTable.organizationId, organizationId as OrganizationId)))
    .limit(1);

  const provider = providerRows[0] ?? null;
  if (!provider || !verifyStoredScimToken({ storedToken: provider.scimToken, rawToken })) {
    return null;
  }
  return provider;
}

Tokens are stored hashed (SCIM_TOKEN_STORAGE_STRATEGY = "hashed") and verified via verifyStoredScimToken. This prevents raw token exposure and supports safe rotation. The implementation also includes retry-back-off logic through SCIM_SYNC_RETRY_BASE_MS and SCIM_SYNC_MAX_ATTEMPTS configuration.

Step-by-Step Integration Guide

Follow these steps to complete your OpenWork Microsoft Entra ID SSO and SCIM provisioning setup:

1. Prepare the Entra Enterprise Application

  • Create a new non-gallery application or reuse an existing one
  • Assign at least one test user for validation
  • Note the Microsoft Entra Identifier (IdP Issuer) and Login URL (Entry Point)

2. Configure SAML in OpenWork

Navigate to Dashboard → SSO → SAML and enter:

  • IdP Issuer URL: Your Entra identifier (e.g., https://sts.windows.net/<tenant-id>/)
  • Domain: Your organization's email domain
  • SAML Entry Point: Entra login URL
  • Audience URL: Usually your OpenWork base URL
  • IdP Certificate: Base64-encoded X.509 certificate from Entra

Save the connection to generate OpenWork's ACS URL and Metadata URL.

3. Exchange Metadata with Entra

In your Entra enterprise application's Single sign-on settings:

  • Paste OpenWork's ACS URL into Reply URL (Assertion Consumer Service URL)
  • Paste OpenWork's Audience URL into Identifier (Entity ID)
  • Download Entra's Federation Metadata XML if needed for verification

4. Enable SCIM Provisioning

In OpenWork, go to Dashboard → SCIM:

  • Copy the SCIM base URL: https://<your-openwork-domain>/api/auth/scim/v2
  • Generate a bearer token — this displays once; copy it immediately

5. Configure Provisioning in Entra

In your Entra application, select Provisioning:

  • Set Provisioning Mode to Automatic
  • Enter the SCIM base URL as Tenant URL
  • Enter the bearer token as Secret Token
  • Click Test Connection to validate

Configure attribute mappings:

  • userName → userPrincipalName or mail
  • displayName → standard mapping
  • emails[type eq "work"].value → primary email

Enable group provisioning only if you plan to sync Entra groups to OpenWork teams.

6. Validate the Integration

Test Expected Result
Visit /sso/<org-slug> Redirects to Microsoft login page
Complete Entra authentication OpenWork creates/updates user and organization membership
Attempt email/password login for managed user Returns "SSO required" error
Entra Test Connection Success with valid bearer token
Assign user in Entra User appears in OpenWork member list
Remove user assignment in Entra ScimUserTombstoneTable records deprovisioning

Troubleshooting Common Issues

Error Cause Solution
"Reply URL is invalid" ACS URL mismatch Copy the exact ACS URL from OpenWork post-save and update Entra's Reply URL
AADSTS700016 with issuer URL Entra using IdP issuer as SP Entity ID Set Entra Identifier (Entity ID) to OpenWork's audience URL, not the issuer
SCIM "Test Connection" fails with 401 Token copied incorrectly or rotated Regenerate token in OpenWork Dashboard → SCIM and update Entra's Secret Token
Group provisioning errors OpenWork SCIM group sync limitations Disable group object mapping in Entra; use group assignment for user scoping only
User not created on SCIM sync Attribute mapping mismatch Verify userName maps to a valid email attribute in Entra

For comprehensive troubleshooting, reference the official guide at [docs/microsoft-entra-sso-scim.md](https://github.com/different-ai/openwork/blob/dev/docs/microsoft-entra-sso-scim.md).

Code Implementation Details

Verifying SCIM Bearer Tokens

Internal handlers use resolveScimProviderFromBearerToken to authenticate incoming SCIM requests:

import { resolveScimProviderFromBearerToken } from "./scim.js";

async function handleScimRequest(req) {
  const token = req.headers.get("Authorization")?.replace(/^Bearer\s+/i, "");
  const provider = await resolveScimProviderFromBearerToken(token);
  if (!provider) throw new Error("Invalid SCIM token");
  // provider contains organizationId, providerId for request routing
}

Source: [scim.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/scim.ts), lines 30-55

Rotating SCIM Tokens Programmatically

Administrators can rotate tokens via the internal API:

import { rotateOrganizationScimToken } from "./scim.js";

const { connection, scimToken } = await rotateOrganizationScimToken({
  organizationId: "org_01H2ABCDEF1234567890",
  headers: new Headers({ cookie: "session=…" })
});
// scimToken displays once — copy to Entra immediately

Source: [scim.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/scim.ts), lines 191-214

Key Source Files

File Purpose
[docs/microsoft-entra-sso-scim.md](https://github.com/different-ai/openwork/blob/dev/docs/microsoft-entra-sso-scim.md) Complete user-facing integration guide
[ee/apps/den-api/src/sso-entra-domain.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/sso-entra-domain.ts) Entra domain detection (isMicrosoftEntraManagedDomain)
[ee/apps/den-api/src/scim.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/scim.ts) SCIM token resolution, provisioning sync, retry logic
[ee/apps/den-api/src/sso.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/sso.ts) SSO routes and ACS handling
[ee/apps/den-api/src/routes/org/oauth-providers.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/routes/org/oauth-providers.ts) Entra tenant validation on OAuth provider creation

Summary

  • OpenWork Microsoft Entra ID integration uses SAML for SSO and SCIM 2.0 for user provisioning, built on Better Auth with organization-scoped policies
  • Automatic domain detection occurs when the IdP Issuer URL matches sts.windows.net or login.microsoftonline.com patterns
  • SCIM authentication uses hashed bearer tokens with encoded organization and provider context, preventing token leakage
  • Metadata exchange requires copying OpenWork's generated ACS URL and Audience URL into Entra's SAML configuration
  • Troubleshooting focuses on URL mismatches, token rotation, and attribute mapping alignment

Frequently Asked Questions

What SAML versions does OpenWork support for Microsoft Entra ID?

OpenWork supports SAML 2.0 through Better Auth's implementation. The integration requires signed assertions and validates issuer, timestamps, and certificate chains. Deprecated signature algorithms are rejected according to the security policy in [scim.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/scim.ts).

Can I use OpenID Connect instead of SAML with Microsoft Entra ID?

Yes. While this guide focuses on SAML, OpenWork also exposes /v1/sso/oidc endpoints. The Entra detection logic in [sso-entra-domain.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/sso-entra-domain.ts) is specific to SAML issuer URLs; OIDC configurations use standard OAuth 2.0 discovery without automatic domain verification.

How does OpenWork handle SCIM user deprovisioning?

When Entra removes a user assignment, OpenWork creates a tombstone record in ScimUserTombstoneTable rather than hard-deleting the user. This preserves audit history while disabling organization access. The deprovisioned user's external identity remains marked inactive, and SSO-required enforcement prevents re-authentication through email/password.

Is group provisioning required for OpenWork SCIM?

No. Group provisioning is optional and currently has limitations in OpenWork. You can disable group object mapping in Entra and rely solely on user provisioning. If enabled, groups sync to OpenWork's Teams feature with member assignments, but verify compatibility with your OpenWork version as group SCIM support varies.

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 →