Skip to main content

Configure single sign-on (SAML / OIDC)

Measured Cloud supports SAML 2.0 and OpenID Connect identity providers. Members of your org sign in via your IdP; users are JIT-provisioned (just-in-time created) the first time they log in.

Goal​

Wire one of your identity providers — Okta, Azure AD, Google Workspace, etc. — to Measured Cloud, sign in with a corporate identity, and (optionally) disable the email + password login path for your org.

Prereqs​

  • The super admin role in Measured Cloud. Only a super admin can add, change, enable, enforce or delete a provider, because a provider decides whose sign-in is trusted. A technical admin can see the providers, but those controls are disabled for them.
  • A running Measured Cloud with PUBLIC_BASE_URL set to the public hostname your IdP will redirect to (e.g. https://dq.example.com). The callback URL is built from this — see Configuration.
  • For OIDC: the IdP's issuer URL, client ID, and client secret.
  • For SAML: the IdP's entity ID, single-sign-on (SSO) URL, and the X.509 certificate that signs assertions.

How secrets are stored​

Client secrets and IdP private keys are encrypted at rest under your org's data-encryption key. The encryption envelope is HARDEN-16: each org owns a Data Encryption Key (DEK) which is itself wrapped under the Key Encryption Key (KEK) configured for the deployment. A leaked DB backup never exposes IdP secrets unless the KEK leaks too.

The UI list view replaces ciphertext fields with <field>_set: true markers; the secret never round-trips through the browser after it's saved.

Add an OIDC provider​

Pick a provider template​

Settings → Single sign-on → Add provider opens with a Provider picker offering Okta, Microsoft Entra ID, Google Workspace, SAML (custom), and OIDC (custom).

Pick a named provider and the modal:

  • sets the kind to OIDC,
  • prefills the issuer — a fixed value for Google Workspace (https://accounts.google.com), or swaps the placeholder to that provider's issuer pattern for Okta (https://{your-domain}.okta.com/oauth2/default) and Entra ID (https://login.microsoftonline.com/{tenant-id}/v2.0), and
  • shows provider-specific step-by-step setup hints plus a link to that IdP's admin console.

Once the issuer is filled, click Discover as usual to resolve the concrete OIDC endpoints from the well-known document (see the per-provider sub-sections below). Choosing OIDC (custom) or SAML (custom) skips the templated guidance and gives you the plain form. The default selection is OIDC (custom).

Google Workspace SSO is configured here, in the SSO settings, using https://accounts.google.com as the issuer — the same place as every other OIDC provider.

Okta​

  1. In Okta, Applications → Create App Integration → OIDC, Web app.
  2. Sign-in redirect URI: https://dq.example.com/auth/sso/<your-org-slug>/callback.
  3. Sign-out redirect URI: https://dq.example.com/login.
  4. Copy the Client ID, Client secret, and Okta domain (the issuer is https://<domain>).

Azure AD​

  1. In the Azure portal, App registrations → New registration.
  2. Redirect URI: https://dq.example.com/auth/sso/<your-org-slug>/callback (Web).
  3. Certificates & secrets → New client secret, copy it.
  4. The issuer URL is https://login.microsoftonline.com/<tenant-id>/v2.0.

Google Workspace​

  1. Cloud Console → APIs & Services → Credentials → OAuth client ID.
  2. Application type: Web; authorised redirect URI: https://dq.example.com/auth/sso/<your-org-slug>/callback.
  3. The issuer URL is https://accounts.google.com.
  4. Copy the client ID + secret.

In Measured Cloud​

  1. Settings → Single sign-on → Add provider.
  2. Provider: pick Okta, Microsoft Entra ID, or Google Workspace (or OIDC (custom) for any other OIDC IdP). This sets the kind to OIDC and prefills the issuer.
  3. Paste / confirm the issuer URL, then client ID and client secret.
  4. Choose the default role for JIT-provisioned users: viewer, editor, technical_admin or super_admin (viewer is safest; bump them up once you've confirmed they're in the right org). Only a super admin can pick technical_admin or super_admin — every first-time login through the provider would otherwise mint a new admin. The retired owner / admin values are rejected.
  5. Leave Enforce unchecked the first time — that way you can still fall back to password login if the IdP config has a typo.
  6. Save.

Add a SAML provider​

Note: SAML support is an optional install. Self-hosted deploys need the dq-cloud-api[saml] extra (which depends on python3-saml, which in turn depends on the libxml2 / xmlsec system libraries). If the extra isn't installed, the SAML endpoints return HTTP 501 with a clear install hint and OIDC continues to work.

Okta​

  1. Applications → Create App Integration → SAML 2.0.
  2. Single sign-on URL (ACS): https://dq.example.com/auth/sso/<your-org-slug>/callback.
  3. Audience URI (SP entity ID): https://dq.example.com.
  4. After saving, View setup instructions gives you the IdP's SSO URL, Entity ID, and X.509 certificate.

In Measured Cloud​

  1. Settings → Single sign-on → Add provider.
  2. Kind: SAML.
  3. Paste the entity ID, SSO URL, and the IdP's X.509 certificate (-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----).
  4. (Optional) Paste the full metadata XML if you'd like the toolkit to discover the SSO endpoints by itself.
  5. Pick a default role and Save.

Test the login flow​

  1. Open https://dq.example.com/login in an incognito window.
  2. Click Sign in with SSO.
  3. Type your org slug. Measured Cloud redirects you to the IdP; complete the IdP's login.
  4. The IdP bounces you back to /auth/sso/<slug>/callback. You should land in the Measured Cloud home page, signed in.

A first-time SSO login (with auto-provisioning on for the provider) creates a users row with hashed_password = NULL and external_identity = {idp_id, subject}, an org membership at the provider's default role, and a membership in the org's default workspace (or its oldest one) at the matching workspace role — so the new user lands on a working app. Subsequent logins look the user up by (idp_id, subject) directly. An existing account is matched by email ignoring case, so Jane@Example.com and jane@example.com are the same person.

When sign-in fails​

The callback never shows a raw error page. Any failure sends the browser back to /login?sso_error=<code>, and the login page explains it in plain words. The server log (sso_callback_failed) records the detail. The codes:

CodeWhat happened
state_invalidThe sign-in link expired, was already used, or named an unknown org. Try again.
idp_errorThe identity provider returned an error, or its token or assertion didn't verify.
email_unverifiedThe provider didn't confirm the email address, so it can't be matched to an existing account.
not_provisionedThe account isn't set up in this org and auto-provisioning is off. Ask a super admin or workspace admin to invite you.
other_orgThe email belongs to an account in a different organization that can't sign in this way. Use your own organization's sign-in or your password.
link_conflictThe account is already linked to a different sign-in provider, or the org has several providers (so an email match is ambiguous). Ask a super admin to resolve it.
no_membershipThe account has no organization membership.
org_suspendedThe organization is suspended.
account_disabledThe account is deactivated.
admin_link_refusedThe provider's email matches a super admin or technical admin account that isn't linked to this provider yet. Admin accounts are never linked automatically. Sign in with your password, then use Link my account (below).
sso_failedAnything else. Try again; if it persists, check the server log.

"Sign in with Google" for users of other orgs​

A deployment can show a one-click Sign in with Google button that starts one org's Google provider (DEFAULT_SSO_SLUG in the UI's runtime config). Someone whose account belongs to a different org can use that button too: when the provider's issuer is in SSO_CROSS_ORG_TRUSTED_ISSUERS (Google, https://accounts.google.com, by default) and it confirms the email (email_verified: true), they're signed in to their own org. Google is the authority for that mailbox, the same proof a password-reset email relies on.

This only works when all of these hold. Otherwise the login fails with other_org, as it always did:

  • the issuer is on the trusted list. That means the issuer URL configured on the provider, the issuer its discovery document names, and the ID token's iss all have to be on it. A customer-run IdP (Okta, Entra, a self-hosted issuer) can assert any email it likes, so it is never trusted across orgs;
  • for Google, the address is one Google hosts: a Gmail address, or a Google Workspace address whose ID token carries a matching hd (hosted domain) claim. A personal Google account created on some other company's address keeps email_verified: true even after that mailbox is taken away, so it doesn't count;
  • the account belongs to exactly one org, and isn't a super admin or technical admin there. Admin accounts always sign in through their own org: with their password, or through a provider they've linked;
  • that org doesn't enforce its own SSO.

The cross-org path never links the account to the other org's provider and never adds a membership there. The sso_login audit row is written to the user's own org with cross_org: true and the provider's org id. Set SSO_CROSS_ORG_TRUSTED_ISSUERS= (empty) to turn it off.

Enforce SSO​

Once SSO is working, edit the provider and tick Enforce. Password login for any member of your org will then return HTTP 403 with code: sso_required and point the caller at the SSO start endpoint.

Tip: don't enforce until you have at least one super admin who has successfully completed an SSO login. Enforcement disables the password form across the board.

When someone signs in through a provider for the first time, the provider's verified email is normally matched to their existing account and the two are linked. That automatic link is never made for a super admin or technical admin account: anyone who could configure a provider could otherwise assert that account's email and sign in as its owner. Such a sign-in fails with admin_link_refused.

A super admin or technical admin links their own account instead. Sign in with your password, open Settings → Single sign-on and choose the link icon on the provider's row (its tooltip reads Link my account to provider name). You'll be asked for your password again, then sent through the provider's sign-in. The provider identity is linked to your account only (the request carries your user id, and the provider's email must still match it). After that you sign in through the provider like anyone else. Do this before you enforce SSO.

Trouble­shooting​

SymptomLikely cause
OIDC discovery failed: HTTP 404Wrong issuer URL — confirm https://<issuer>/.well-known/openid-configuration returns 200 from the API host.
ID token verification failed: Invalid audienceClient ID in Measured Cloud doesn't match the one configured at the IdP.
ID token nonce mismatchThe IdP didn't echo the nonce. Confirm you're using the authorization-code flow (not implicit).
sso_error=state_invalid on the login pageThe user took > 5 minutes between /start and the IdP callback, or the state was already consumed. Have them retry.
sso_error=other_org for a user of another orgSee "Sign in with Google" for users of other orgs: the issuer isn't trusted, Google doesn't host the address (no matching hd), the user is in several orgs, or their org enforces its own SSO.
SAML response invalid: ...The IdP's X.509 certificate in Measured Cloud doesn't match the one that signed the assertion. Re-copy from the IdP.
501 on a SAML endpointpython3-saml isn't installed. pip install 'dq-cloud-api[saml]' and restart the API.