# Single sign-on (SAML)

> Connect a SAML identity provider, verify your email domains, map groups to roles and require SSO.

Source: https://critlab.app/docs/enterprise/sso

Connect a workspace to your SAML identity provider — Okta, Microsoft Entra ID, Google Workspace, OneLogin, Keycloak or any SAML 2.0 IdP. People then sign in through the IdP, get the right role from their groups, and, if you choose, can reach the workspace no other way.

SAML runs inside web: no extra service, no outbound call at sign-in, and nothing to configure by environment variable. It is an Enterprise feature (on self-hosted installs, part of the Enterprise licence) and is configured by a workspace **owner** in **Settings → Single sign-on**, or with `crit sso`.

## 1. Give your IdP critlab's details

The settings page shows these before you save anything, so the IdP side can be set up first:

| IdP field | Value |
| --- | --- |
| Metadata URL | `https://critlab.example.com/api/auth/sso/saml2/sp/metadata?providerId=ws-<workspace-slug>` |
| ACS (reply) URL | `https://critlab.example.com/api/auth/sso/saml2/sp/acs/ws-<workspace-slug>` |
| Entity ID / audience | Your app origin, e.g. `https://critlab.example.com` |
| Name ID | The user's **email address** |

Most IdPs need only the metadata URL. The provider id is fixed when the connection is first created, so renaming the workspace later never changes a URL already configured in the IdP.

critlab requires **signed assertions**. Authentication requests are not signed.

To send group memberships for role mapping, add a groups attribute to the assertion (in Okta, a group attribute statement; in Entra ID, a groups claim).

## 2. Give critlab your IdP's details

Paste one of:

- the IdP's **metadata URL** (fetched once by web when you save),
- the **metadata XML**, or
- the entity ID, single sign-on URL and signing certificate by hand.

Metadata always wins over hand-entered values. Several signing certificates can be stored at once, so rotating the IdP's certificate is a non-event: add the new one, switch in the IdP, remove the old one.

A metadata URL on your internal network is refused unless the operator sets `SSO_ALLOW_PRIVATE_METADATA_HOSTS=1`; alternatively paste the XML.

## 3. Map groups to roles

Set a **default role** for people the IdP sends without a mapped group, and a role map from IdP group names to critlab roles, for example `critlab-admins` → admin, `contractors` → guest. Group names match case-insensitively; when someone is in several mapped groups, the strongest role wins.

Roles are applied at every sign-in, not just the first, so removing someone from a group demotes them the next time they sign in. The last owner of a workspace is never demoted this way. Groups can grant `guest`, but never folder access: an owner or admin grants a guest's folders afterwards. See [Roles and guest access](/docs/enterprise/roles).

The first time someone signs in through the IdP, critlab creates their account and membership (just-in-time provisioning). To create and remove people ahead of sign-in, add [Directory sync](/docs/enterprise/scim).

## 4. Verify your email domains

Claim the email domains your people use (e.g. `acme.com`) under **Email domains**. Each claim is verified by DNS:

| Record | Type | Value |
| --- | --- | --- |
| `_critlab-verify.acme.com` | TXT | `critlab-domain-verification=<token shown in settings>` |

Once a domain is verified and the connection is enabled, anyone who types an address at that domain on the sign-in page is sent straight to your IdP instead of receiving an email code. Matching is exact: `acme.com` does not cover `mail.acme.com`. A domain can be verified by only one workspace, and public email providers can never be claimed. Verification is re-checked periodically.

People can also start at `/login/sso` and pick the workspace, which works before any domain is verified.

## 5. Require SSO (optional)

**Require SSO** makes the workspace unreachable with any session that did not come through its IdP. It applies on the next request, including to sessions already open and to the owner who switched it on, and switching it off restores access without signing in again. The connection must be enabled first (enabling needs at least one claimed email domain), and switching enforcement on re-checks every domain's DNS record before it takes effect. Other workspaces the same people belong to are unaffected. From the CLI or an agent, `crit sso enforce on` needs an explicit `--confirm`, because an API key has no grace window to undo it.

- **API keys** for the `crit` CLI and coding agents keep working by default, since a person minted them after signing in. One switch turns that off for the strictest reading.
- **Lockout safety:** for 10 minutes after turning it on (`SSO_ENFORCE_GRACE_SECONDS`), the session that flipped the switch can still open the Single sign-on settings page to turn it back off. Set the variable to `0` to disable that window.

Refused requests answer `login_required` with a link to `/login/sso?workspace=<slug>`.

## Google and Microsoft sign-in

Separately from SAML, an instance can offer "Continue with Google" and "Continue with Microsoft" to everyone by setting `GOOGLE_CLIENT_ID`/`GOOGLE_CLIENT_SECRET` or `MICROSOFT_CLIENT_ID`/`MICROSOFT_CLIENT_SECRET` (and optionally `MICROSOFT_TENANT_ID` to restrict to one tenant). Register `https://critlab.example.com/api/auth/callback/google` or `…/microsoft` as the redirect URI. These are instance-wide OIDC sign-in options, not per-workspace SSO, and do not satisfy **Require SSO**.

## Audit trail

Configuration changes, enabling and enforcement, sign-ins, provisioning, role changes and domain verification are all recorded: `sso.updated`, `sso.enabled`, `sso.disabled`, `sso.enforcement_changed`, `sso.login`, `sso.provisioned`, `sso.role_synced`, `sso.role_sync_skipped`, `sso.domain_*`. See [Audit log](/docs/enterprise/audit-log).

> **Open questions**
>
> - **Signed authentication requests and encrypted assertions** are not supported; no SP signing key is generated. Whether a customer IdP that requires them is in scope is open.
> - **More than one IdP per workspace** is not supported.
> - **Tested IdPs.** The flow is tested end to end against SimpleSAMLphp. Okta and Entra ID are expected to work with email as the Name ID; neither has been verified against a live tenant in the repository's tests.
