Skip to main content
This page covers the two Enterprise-only capabilities in this group: single sign-on (SSO) for your team, and self-hosted deployment of the ModuleX backend. Both are gated by per-plan entitlement flags that are set only on the Enterprise plan; everything below describes how each capability works against the verified backend behavior, what it requires, and which specifics are not pinned in source and are marked TBD. Authentication mechanics referenced here are documented in full on the authentication model page; credential and secret encryption is documented on the data security & encryption page.
Both capabilities are Enterprise entitlements. In the plan configuration, feature.sso and feature.self_host are true only on the Enterprise plan and false on Free, Pro, and Max. They are surfaced as account feature flags; they are not toggles you set yourself. Talk to the team to enable them — see contact sales.

How SSO works in ModuleX

ModuleX has one identity provider: Clerk. The backend only ever verifies Clerk-issued JWTs — there are no first-party /auth/login, /register, /logout, or /refresh routes. The auth provider is read from modulex.toml as [auth].provider, which defaults to "clerk" and is the only configuration key that file consumes:
modulex.toml
Because every human sign-in flows through Clerk, enterprise SSO is delivered as a Clerk enterprise connection (for example SAML or OIDC against your identity provider). When an SSO user signs in, Clerk mints a JWT, and the ModuleX backend verifies that JWT exactly as it would any other Clerk token. No separate SSO code path exists inside the ModuleX backend.
SAML/OIDC connection details are configured in the Clerk dashboard, not in this repository — TBD. The ModuleX source contains no SAML, OIDC, or SCIM implementation; the feature.sso flag is an entitlement signal, not enforcement code. The exact list of supported identity providers, the SAML/OIDC connection setup steps, the metadata/ACS URLs, and whether SCIM directory sync is offered are not pinned in source. Treat those specifics as TBD and confirm them with the ModuleX team before relying on them.

What the backend verifies on every request

Whether a user arrives via SSO or ordinary Clerk sign-in, the token they carry is verified the same way. The verifier requires both CLERK_JWKS_URL and CLERK_ISSUER to be set; if either is missing, verification returns no claims and the request is rejected.
string
required
JWKS endpoint used to verify the JWT signature. JWKS responses are cached in-process with a 300-second TTL. Missing value disables JWT verification entirely.
string
required
Expected iss claim. The token is decoded with issuer=CLERK_ISSUER.
The claims read downstream are sub (the Clerk user id), email, azp, and sid. When the configured provider is clerk, the effective organization role is sourced from the JWT’s metadata.role. See the authentication model page for the full header-by-header breakdown, including the Authorization: Bearer forms and the Socket.io handshake.

Just-in-time user provisioning

ModuleX provisions accounts just in time (JIT) on the first valid JWT for an unknown user. This is what makes SSO usable without manual seat setup: a user who signs in through your identity provider for the first time is created automatically.
1

First authenticated request arrives

A Clerk JWT for a user with no existing ModuleX record reaches the backend.
2

The user record is created

A User is created with the user-level role USER. (User-level roles are USER and SUPER_ADMIN; these are distinct from organization roles.)
3

A default organization and owner membership are seeded

A personal default organization is created, and the new user is added as its owner. The flow is idempotent and concurrent-safe, guarded by a row lock so a webhook-plus-JIT race cannot double-provision.
There is no JIT path for API keys. A mx_live_* API key cannot create a user — key creation is itself JWT-gated (POST /api-keys requires a signed-in user). So SSO/JIT provisioning applies to human sign-ins only; programmatic callers must already correspond to a provisioned user. See the SDKs overview for the programmatic path.

Organization roles after SSO sign-in

Once provisioned, a user acts inside an organization under an organization role. The live roles are owner and admin only.
The member role is retired. It was retired on the backend and may still appear on old organization rows, in legacy invitation constraints, and in the realtime read-gate, but the REST edge rejects it everywhere (invite and role-update schemas enforce admin only). Document and plan around owner/admin. Note also that the agentic surfaces — Composer, Assistant, and schedules — require owner/admin, despite an older in-code comment suggesting any member can use the Assistant. Full detail is on the roles & permissions page.
owner is assignable only at organization creation. Invitations and role updates are constrained to admin at the API edge. Organization scope itself is carried by the X-Organization-ID header on REST/SDK calls and by the Socket.io handshake auth.organizationId field on the realtime surface — see org context & X-Organization-ID.

Deployment options

ModuleX runs in two shapes. The default is ModuleX-managed (cloud); self-hosted is an Enterprise-only entitlement.

ModuleX-managed (cloud)

The default. ModuleX operates the backend, realtime server, database, and secret storage. Available on every plan. Authentication, secrets, and the production security gate are managed for you.

Self-hosted

Enterprise only (feature.self_host = true). You operate the ModuleX backend in your own environment and supply the required configuration and secrets described below. Talk to the team via contact sales.
Self-hosting is gated by the self_host entitlement. In the plan configuration, feature.self_host is true only on Enterprise. There is no public self-service installer documented in source. The packaging, container images, and orchestration topology for a customer self-host are TBD — confirm the supported deployment artifacts with the ModuleX team.

Self-host requirements

A self-hosted backend sources its configuration through a single settings layer, resolving each secret environment variable first, then Azure Key Vault, then default. The following are the configuration values a production self-host depends on. Types and defaults are as defined in the backend configuration.
string
default:"development"
required
Canonical environment name; drives all production-only enforcement. Aliases are normalized: prod becomes production, stage becomes staging. Set to production for a real deployment. The values production and staging are the two that trigger the hard security gate below.
string (≥32 chars)
required
Symmetric key used to encrypt integration credentials. Required in production/staging (minimum length 32 = 256-bit). In development it defaults to an empty string and the app logs an insecure-key warning. Generate with python3 -c "import secrets; print(secrets.token_urlsafe(32))".
If this key changes, all previously encrypted credential data becomes unrecoverable. Treat it as permanent for the lifetime of the data.
string (≥32 chars)
required
A server-side secret mixed into API-key hashing to harden the one-way fingerprint. Required in production/staging (minimum length 32). Generate with python3 -c "import secrets; print(secrets.token_hex(32))".
API_KEY_PEPPER is production-required. Set it explicitly when you self-host — the startup security gate refuses to start without it.
string
default:"(empty)"
required
PostgreSQL DSN. Required in production/staging (empty value is an error in those environments, a warning otherwise). Use the async driver form, for example postgresql+asyncpg://user:pass@db:5432/modulex.
string
default:"redis://localhost:6379"
Redis connection string. Used for caching, rate limiting, and realtime coordination.
string
default:"(empty)"
required
JWKS endpoint for verifying Clerk JWTs. Required for any human sign-in (including SSO). Missing value disables JWT verification.
string
default:"(empty)"
required
Expected JWT iss claim; must match the issuer your Clerk instance mints.
comma-separated list
default:"[]"
Gate for the platform /admin/* cross-organization monitoring routes. Comma-separated to allow zero-downtime rotation. An empty list makes the admin gate fail closed (no access). Generate with openssl rand -hex 32.
string
default:"(unset)"
When set, secrets are sourced from Azure Key Vault after the environment variable lookup. Authentication uses the Container App system-assigned managed identity with the “Key Vault Secrets User” role. When unset (local/dev), only environment variables are read.
Casing on the wire. Environment variables are UPPER_SNAKE_CASE. When sourced from Azure Key Vault, secret names are UPPER-KEBAB-CASE (underscores become hyphens) — for example ENCRYPTION_KEY is stored as ENCRYPTION-KEY and API_KEY_PEPPER as API-KEY-PEPPER. The conversion is automatic.
Key Vault rotation needs a restart. The Key Vault secret resolver is cached for the process lifetime, so rotating a secret in the vault is not picked up until the process restarts. Environment-sourced values are likewise read once at startup.

The production startup security gate

On startup, the backend validates the security-critical secrets. The behavior depends on ENVIRONMENT: In production/staging, any validation error prints ❌ SECURITY VALIDATION FAILED and the process exits with code 1. In development the process prints an insecure-settings warning and continues. On success it logs that security settings were validated.

Minimal production environment

Operational details that are not pinned in source — TBD. The process/worker runtime (host, port, worker count), container images and orchestration topology, backup/restore procedures, and any Celery or telemetry configuration are not documented in the configuration source. The backend exposes no PORT, WORKERS, HOST, or SENTRY_* environment variables, which suggests those are supplied by the process manager or compose stack rather than by application config. Confirm the supported runtime with the ModuleX team.

Provisioning and seats

User provisioning is JIT, as described above — new SSO users are created on first sign-in with an owner default organization. Directory-driven provisioning and de-provisioning (SCIM) is not implemented in source and is TBD. Seat limits come from plan entitlements, not from a deployment setting: A seats quota of null means the plan is per-seat licensed (billed, not capped at a fixed number). Enterprise sets feature.self_host and feature.sso to true; all other plans set both to false. For the full plan comparison and the credit and rate-limit allowances, see plans & pricing.
Enterprise annual pricing is unresolved — present both figures. The marketing site advertises Pro annual at $240/yr and Max annual at $960/yr (a 20% discount), while the backend Stripe configuration prices the annual SKUs at $300/yr and $1,200/yr (full monthly × 12, no discount). The authoritative annual price is an open question; do not pick a winner here. Always confirm the actual charge at checkout, and see plans & pricing for the canonical figures.

Requirements at a glance

  • An Enterprise plan with feature.sso = true.
  • A Clerk enterprise connection configured against your identity provider (SAML/OIDC). Connection setup lives in the Clerk dashboard and is TBD in this documentation.
  • CLERK_JWKS_URL and CLERK_ISSUER configured on the backend (managed for you on cloud).
  • Users are provisioned JIT on first sign-in as organization owner of a default org; live roles are owner/admin.
  • An Enterprise plan with feature.self_host = true.
  • ENVIRONMENT=production (or staging).
  • ENCRYPTION_KEY and API_KEY_PEPPER, each at least 32 characters — both production-required. Set API_KEY_PEPPER explicitly when you self-host.
  • A PostgreSQL DATABASE_URL (async driver) and a REDIS_URL.
  • CLERK_JWKS_URL and CLERK_ISSUER for sign-in.
  • ADMIN_DASHBOARD_API_KEYS if you use the platform admin dashboard (the gate fails closed when empty).
  • Optionally, AZURE_KEY_VAULT_URL to source secrets from Azure Key Vault.
  • The startup security gate exits the process with code 1 if any required secret is missing or too short in production/staging.
  • Container images, orchestration, and runtime topology are TBD — confirm with the ModuleX team.

Authentication model

Clerk JWT verification, mx_live_* API keys, and the header forms every caller uses.

Data security & encryption

How credentials and secrets are encrypted, and the production security checks.

Roles & permissions

The live owner/admin role model and what each can do.

Contact sales

Enable SSO and self-hosting on an Enterprise plan.