# How to Configure Single Sign-On (SSO) using SAML or OIDC with OpenWork

> Configure Single Sign-On SSO SAML or OIDC with OpenWork. Enterprises register an identity provider via the Den API and verify domain ownership.

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

---

**Enterprises configure SSO in OpenWork by registering a SAML or OIDC provider through the Den API, verifying domain ownership via DNS TXT records, and enabling automatic discovery or manual endpoint configuration.**

OpenWork, developed under the `different-ai/openwork` repository, implements enterprise Single Sign-On through its Den API service. The architecture separates provider registration, connection metadata storage, and domain verification into distinct stages, enabling secure integration with corporate identity providers. This guide details the exact API endpoints, source file locations, and security policies required to configure SSO using SAML or OIDC protocols.

## Architecture and Prerequisites

OpenWork stores SSO configuration across two database tables managed by the Den API: the `SsoProvider` table holds the Better Auth provider credentials, while the `SsoConnection` table maintains organizational metadata including issuer, domain, and endpoint URLs. Before configuring SSO, the system validates that the workspace holds the appropriate enterprise entitlement.

### Verify Enterprise Entitlements

Only workspaces with the `sso` entitlement can manage SSO connections. The API rejects unauthorized requests early in the middleware chain via the `checkEntitlement(..., "sso")` function defined in **[`ee/apps/den-api/src/routes/org/sso.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/org/sso.ts)**.

## Configuring SAML SSO

SAML integration requires registering the identity provider, retrieving Service Provider metadata, and enforcing strict security policies.

### Register the SAML Provider

Send a POST request to `/v1/sso/saml` with the identity provider's metadata. The route handler in **[`ee/apps/den-api/src/routes/org/sso.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/org/sso.ts)** validates the payload using Zod, then invokes `registerOrganizationSsoConnection`, which calls `auth.api.registerSSOProvider` to persist the configuration in the `SsoProvider` table.

```bash
curl -X POST https://<openwork-host>/api/v1/sso/saml \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "issuer": "https://saml-idp.example.com",
    "domain": "example.com",
    "entryPoint": "https://saml-idp.example.com/sso",
    "cert": "MIID...AB",
    "audience": "https://app.openwork.com"
}'

```

### Retrieve SAML Service Provider Metadata

After registration, obtain the SP metadata to upload to your identity provider by calling GET `/v1/sso/metadata?format=xml` (or `json`). This endpoint, also defined in **[`ee/apps/den-api/src/routes/org/sso.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/org/sso.ts)**, returns the metadata generated by Better Auth.

```bash
curl -H "Authorization: Bearer <admin-token>" \
  "https://<openwork-host>/api/v1/sso/metadata?format=xml"

```

## Configuring OIDC SSO

OIDC configuration supports automatic discovery of endpoints or manual specification for air-gapped environments.

### Register the OIDC Provider

Submit a POST request to `/v1/sso/oidc` with the issuer URL, client credentials, and desired scopes. The handler processes the request through the same `registerOrganizationSsoConnection` flow in **[`ee/apps/den-api/src/sso.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/sso.ts)**.

```bash
curl -X POST https://<openwork-host>/api/v1/sso/oidc \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "issuer": "https://login.microsoftonline.com/<tenant-id>/v2.0",
    "domain": "example.com",
    "clientId": "<client-id>",
    "clientSecret": "<client-secret>",
    "scopes": ["openid","email","profile"],
    "skipDiscovery": false
}'

```

### OIDC Discovery and Endpoint Resolution

When `skipDiscovery` is `false` (default), OpenWork automatically fetches the discovery document from `/.well-known/openid-configuration` and validates that the `issuer` matches the configured value. If discovery is disabled, you must supply `authorizationEndpoint`, `tokenEndpoint`, and `jwksEndpoint`. This logic resides in the `resolveOidcEndpoints` function within **[`ee/apps/den-api/src/sso.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/sso.ts)**.

## Domain Verification Workflow

Before users can authenticate, OpenWork verifies domain ownership to prevent unauthorized SSO claims.

### Request a DNS Verification Token

Call POST `/v1/sso/request-domain-verification` to receive a unique DNS TXT token. The endpoint is implemented in **[`ee/apps/den-api/src/routes/org/sso.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/org/sso.ts)**.

```bash
curl -X POST https://<openwork-host>/api/v1/sso/request-domain-verification \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{"providerId":"openwork-sso-42"}'

```

Publish the returned token as a TXT record under `_openwork-sso.example.com`.

### Verify Domain Ownership

Once DNS propagates, confirm ownership by calling POST `/v1/sso/verify-domain`. A **204 No Content** response indicates successful verification, enabling the connection for production use.

```bash
curl -X POST https://<openwork-host>/api/v1/sso/verify-domain \
  -H "Authorization: Bearer <admin-token>"

```

## SAML Security Policies

OpenWork enforces strict SAML security defaults defined in **[`ee/apps/den-api/src/sso-saml-policy.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/sso-saml-policy.ts)**. These constants are referenced during provider creation in `registerBetterAuthSsoProvider` within **[`ee/apps/den-api/src/sso.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/sso.ts)**:

- **Signed Assertions Required**: `ORGANIZATION_SAML_WANT_ASSERTIONS_SIGNED` is set to `true`, rejecting unsigned SAML assertions.
- **IdP-Initiated Logins Allowed**: `ORGANIZATION_SAML_ALLOW_IDP_INITIATED` permits identity provider-initiated authentication flows.
- **Timestamp Enforcement**: `ORGANIZATION_SAML_REQUIRE_TIMESTAMPS` ensures strict validation of `NotBefore` and `NotOnOrAfter` conditions.
- **Algorithm Security**: `ORGANIZATION_SAML_DEPRECATED_ALGORITHM_BEHAVIOR` is set to `"reject"`, blocking signatures using weak hash algorithms.

## Initiating User Authentication

End-users begin SSO authentication by navigating to `/sso/:orgSlug`. This route utilizes the `startOrganizationSsoSignIn` helper in **[`ee/apps/den-api/src/sso.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/sso.ts)**, which forwards the request to `auth.api.signInSSO` (Better Auth) to initiate the SAML or OIDC flow. After successful identity provider validation, users are redirected to the organization's callback URL.

## Summary

- **Entitlement enforcement**: The `sso` entitlement is verified via `checkEntitlement` in **[`ee/apps/den-api/src/routes/org/sso.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/org/sso.ts)** before processing any configuration requests.
- **Provider registration**: Use POST `/v1/sso/saml` or POST `/v1/sso/oidc`, handled by `registerOrganizationSsoConnection` and `auth.api.registerSSOProvider` in **[`ee/apps/den-api/src/sso.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/sso.ts)**.
- **SAML security**: Strict defaults in **[`ee/apps/den-api/src/sso-saml-policy.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/sso-saml-policy.ts)** require signed assertions, timestamps, and reject deprecated algorithms.
- **OIDC flexibility**: Automatic discovery via `resolveOidcEndpoints` or manual endpoint configuration using `skipDiscovery`.
- **Domain trust**: DNS TXT verification via POST `/v1/sso/request-domain-verification` and POST `/v1/sso/verify-domain` prevents domain hijacking.

## Frequently Asked Questions

### Does OpenWork support both IdP-initiated and SP-initiated SAML flows?

Yes. The `ORGANIZATION_SAML_ALLOW_IDP_INITIATED` constant in **[`ee/apps/den-api/src/sso-saml-policy.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/sso-saml-policy.ts)** is set to `true` by default, allowing identity providers to initiate login flows while maintaining security through strict assertion validation and audience checks.

### What happens if OIDC discovery fails during provider registration?

If `skipDiscovery` is `false` (the default), OpenWork attempts to fetch the discovery document from `/.well-known/openid-configuration` using the `resolveOidcEndpoints` function in **[`ee/apps/den-api/src/sso.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/sso.ts)**. If discovery fails, registration returns an error. Administrators can set `skipDiscovery: true` and manually provide `authorizationEndpoint`, `tokenEndpoint`, and `jwksEndpoint` to bypass discovery.

### Where is the SSO provider data stored in OpenWork?

Provider credentials and protocol-specific settings are stored in the `SsoProvider` table, while organizational connection details—including domain, verification status, and issuer metadata—are maintained in the `SsoConnection` table. These records are created and managed by the business logic in **[`ee/apps/den-api/src/sso.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/sso.ts)**.

### How do I obtain the Service Provider metadata for my SAML identity provider?

Make an authenticated GET request to `/v1/sso/metadata?format=xml` (or `format=json`) as defined in **[`ee/apps/den-api/src/routes/org/sso.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/org/sso.ts)**. This returns the SP metadata generated by Better Auth, which you must upload to your SAML IdP to establish the trust relationship.