Single sign-on¶
Preloop Cloud and Preloop Enterprise support single sign-on per account with
OIDC or SAML 2.0. An account administrator configures it under Account
settings > Single sign-on; everything there is also available through the API
under /api/v1/accounts/{account_id}/sso.
What the SSO feature does:
- SP-initiated sign-in with OIDC (authorization code with PKCE S256) or SAML 2.0 (HTTP-Redirect request, HTTP-POST ACS).
- Sign-in discovery from a verified email domain.
- Just-in-time membership for new users, with a default role that can never be owner.
- IdP groups mapped to roles and teams on each sign-in.
- Enforcement: members on verified domains must use SSO; account owners keep an emergency password or passkey sign-in, and every use is audited and emailed to all owners.
- An administrator test sign-in that must succeed before enforcement can be turned on.
To create, update and remove members from your IdP, see SCIM provisioning.
Provider guides: Okta, Microsoft Entra ID, Google Workspace. Each uses OIDC, with SAML as the alternative. The generic OIDC and SAML instructions are below.
Deployment settings (Preloop Enterprise)¶
Preloop Cloud is already configured. On a self-hosted Enterprise install, set these Helm values:
environment:
preloopUrl: "https://preloop.example.com" # external HTTPS origin
sso:
enabled: true
publicUrl: "" # optional; defaults to environment.preloopUrl
allowPrivateIssuers: false # see below
| Value | Effect |
|---|---|
sso.enabled |
Mounts the SSO sign-in and administration routes and shows the console tab. With false, the routes are not mounted. Enforcement already on for an account still applies, so turn enforcement off before you disable SSO. |
sso.publicUrl |
The origin used for the OIDC redirect URI and the SAML entity id and ACS URL. Use it when the browser reaches Preloop on a different origin than environment.preloopUrl. Give an origin only, with no path. |
sso.allowPrivateIssuers |
Lets OIDC issuers and SAML metadata URLs resolve to private (RFC 1918, IPv6 unique-local) or loopback addresses, for an IdP on your internal network. It applies when a connection is saved and at sign-in (OIDC discovery, token and JWKS requests); sign-in connects only to the addresses that passed the check. Link-local, cloud metadata (including the AWS and GCP IPv6 metadata addresses), shared and reserved addresses stay refused. It is a core setting that also relaxes gateway identity providers. Single-tenant installs only. |
The Enterprise image includes libxmlsec1 and the python3-saml and xmlsec
packages that SAML needs. The image startup check fails if they are missing.
The browser must send a same-origin Origin or Referer when
starting sign-in. Keep the default referrer policy on the sign-in page.
Before you start: verify a domain¶
- In Verified domains, add your email domain (for example
example.com). Public mailbox domains are refused. - Publish the TXT record shown,
preloop-verification=<token>, on the domain itself, then click Verify DNS.
A domain can be verified by only one Preloop account. Sign-in discovery, email-based linking of existing members and SAML just-in-time membership all depend on a verified domain.
URLs to register with your IdP¶
Each connection shows its own URLs after you save it (<id> is the connection
id):
| Protocol | URL |
|---|---|
| OIDC redirect URI | https://<origin>/api/v1/auth/sso/<id>/callback |
| SAML SP entity id and metadata | https://<origin>/api/v1/auth/sso/<id>/metadata |
| SAML ACS (HTTP-POST) | https://<origin>/api/v1/auth/sso/<id>/acs |
<origin> is sso.publicUrl, or environment.preloopUrl if that is not set.
On Preloop Cloud it is https://preloop.ai.
Generic OIDC¶
Your IdP must:
- publish
/.well-known/openid-configurationover HTTPS and support PKCES256. Preloop always sends S256. If the document listscode_challenge_methods_supported, the list must includeS256; if the field is missing (as with Entra ID), S256 is assumed; - use an exact, tenant-specific issuer (multi-tenant endpoints such as Entra
commonororganizationsare refused); - sign ID tokens with RS256 or ES256;
- issue ID tokens with a lifetime (
exp - iat) of at most 24 hours. The usual one-hour tokens from Okta, Entra ID and Google are accepted.exp,iatandnbfare checked against the clock with 60 seconds of skew.
Steps:
- Create a confidential web application in the IdP that uses the authorization code flow. Leave the redirect URI empty for now.
- In Preloop, click Add OIDC connection. Enter the issuer, client id,
client secret and scopes (
openid email profile, plus the scope that carries groups if your IdP needs one). Choose a default role and whether new users get a membership on first sign-in. Save. - Copy the connection's callback URL into the IdP as the redirect URI.
- Optional: set Groups claim or attribute to the ID-token claim that lists the user's groups, then add group mappings (see Groups).
- Click Test sign-in and complete the sign-in as yourself.
The email comes from the email claim. To read another claim, set
attribute_mapping to {"email": "<claim>"} with
PATCH /api/v1/accounts/{account_id}/sso/connections/{id}; the console does
not edit this field. An email is linked to an existing member only when the
member's email is verified in Preloop and the IdP reports
email_verified: true or the domain is verified on the account.
Generic SAML¶
Preloop requires signed assertions by default; if you turn that off, the Response must still be signed. SHA-1 signatures and digests are refused. Unsolicited (IdP-initiated) responses are refused, so users always start from the Preloop sign-in page. At most 60 seconds of clock skew is allowed.
- Click Add SAML connection and save a name. The connection then shows its SP metadata URL, ACS URL and, once you set an SP signing key, the public SP certificate.
- In the IdP, create a SAML application from the SP metadata URL, or enter the entity id and ACS URL by hand. Set the NameID to a stable, unique identifier for the user.
- Send these attributes:
email, the user's address on your verified domain (required, exactly one value);displayName(optional);- a groups attribute (optional).
If your IdP uses other names, set attribute_mapping (keys email,
display_name) with
PATCH /api/v1/accounts/{account_id}/sso/connections/{id}.
4. Back in Preloop, under Import identity provider metadata, paste the
IdP's metadata URL or XML, click Preview metadata, then Apply metadata
to editor and save. The import reads one IdP only, refuses DTDs and
redirects, and is capped at 256 KiB. The IdP entity ID and sign-in URL can
also be typed in; signing certificates come from the metadata (or the
saml_idp_certs API field). Several certificates are accepted so that you
can rotate the IdP certificate without downtime.
5. Optional: set an SP signing key, an RSA key of at least 2048 bits in
unencrypted PEM. Preloop then signs AuthnRequests and can decrypt encrypted
assertions. The key cannot be read back. Each time you set or rotate it,
re-import the SP metadata into the IdP.
6. Click Test sign-in.
SAML has no email-verified flag. A new membership or a link to an existing member therefore requires the user's email domain to be verified on the account, and linking also requires the member's email to be verified in Preloop.
Groups, roles and teams¶
Set the connection's Groups claim or attribute, then under Group mappings map group values to a role, a team, or both. On each normal sign-in:
- matching grants are added, and grants that this mapping created earlier are removed when the group disappears;
- roles and team memberships that an administrator assigned by hand are kept;
- if several roles match and one includes all the others' permissions, it
wins; if they cannot be compared, the connection's default role applies and
an
ambiguous_role_mappingevent is audited; - the owner role and owner-only permissions cannot be mapped.
Administrator test sign-ins do not apply group mappings. When Entra ID sends a group overage marker instead of the full list, the default role applies; there is no Microsoft Graph lookup.
Enforcement and break-glass¶
Require single sign-on turns enforcement on for the account. You can turn it on only after you have completed a Test sign-in in the last 24 hours on an enabled connection whose configuration has not changed since.
While enforcement is on, members whose email is on a verified account domain cannot use password, social or passkey sign-in, a refresh of a non-SSO session, account switching, or CLI and MCP OAuth exchanges derived from a non-SSO session. When you turn enforcement on, their existing non-SSO sessions are revoked. SSO sessions, and memberships in other accounts, are not affected. API keys and runtime credentials follow their own policies.
Break-glass: every current account owner keeps password and passkey sign-in.
Each emergency sign-in records sso.break_glass.used in the audit log and
emails all owners. To reach an enforced account after using another one, sign
in again with a password or passkey and select that account during the sign-in
(the console uses /login?account_id=<account id>). Switching accounts from an
existing session does not grant the exception. The break_glass_owner_ids
policy field is legacy and informational only; it does not limit which owners
keep emergency access.
Turning enforcement off restores the other sign-in methods. Identity links, passwords and passkeys are kept; sessions that were already revoked stay revoked.
Troubleshooting¶
| Symptom | Check |
|---|---|
| The email step does not offer SSO | The domain is verified and the account has exactly one enabled connection. With several connections, use the connection's own sign-in link. |
| "Start single sign-on from the sign-in page" | The browser sent no same-origin Origin or Referer. Start from the Preloop sign-in page and check proxies that strip Referer. |
| "Unable to start single sign-on" (OIDC) | Discovery failed. Check that the issuer is exact, the endpoints use HTTPS on public addresses (or private ones with sso.allowPrivateIssuers), and that code_challenge_methods_supported, if present, includes S256. |
/console#sso_error=sign_in_failed |
The audit log has an sso.login.failed event with a reason code, for example lifetime_exceeded, bad_audience, missing_email or replayed_state. |
| The IdP rejects the redirect URI or ACS | The URL registered in the IdP must match the connection's URL exactly, including sso.publicUrl. |
Post-deploy probe (Preloop Cloud operations)¶
scripts/prod_sso_probe.py runs after each staging and production deploy. Set
SSO_PROBE_OIDC_EMAIL_<ENV> to enable the enterprise check: the probe signs in
as that user through a probe account whose single OIDC connection points at a
mock IdP that approves without interaction. It runs discover, start, the IdP
redirect and the callback, then calls /api/v1/auth/users/me with the issued
token. SSO_PROBE_SAML_CONNECTION_ID_<ENV> adds a check of that connection's
SP metadata. A failure names the step and fails the job.