> ## Documentation Index
> Fetch the complete documentation index at: https://docs.prelude.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Enforce SSO login

> Require allowlisted email domains to authenticate through SAML SSO.

By default a SAML connection is *one of* several ways a user can sign in — they could still use OTP or a social provider with the same email. Enabling **enforce login** makes SAML the **only** way in for the domains the connection covers.

## How it works

Enforcement is keyed off the connection's `email_domain_allowlist`. When `enforce_login` is `true`, any login attempt whose email domain resolves to that connection is redirected into SAML, and the other methods step aside:

* **OTP create** — submitting an email identifier whose domain is enforced is refused with the [`saml_login_required`](#the-saml_login_required-error) error (HTTP `403`). The SDK is expected to restart the flow via the SAML initiate endpoint.
* **OAuth callback** — when the IdP-supplied email's domain is enforced, the OAuth flow is abandoned (no account is created or linked) and the user is redirected into a fresh SAML `AuthnRequest`. The PKCE challenge and dispatch from the OAuth state are carried over.

Enforcement is a gate layered on top of the existing flows, never a new way for them to fail. A domain that can't be resolved to exactly one enforcing connection — because it isn't allowlisted, or it's ambiguous — is simply **not enforced**, and the normal login methods proceed.

<Warning>
  `enforce_login` is inert without a non-empty `email_domain_allowlist`. The
  allowlist is the only domain→connection binding; with no domains, there is
  nothing to enforce.
</Warning>

## Enable enforcement

Set `enforce_login` to `true` on the connection's behavior block. You can do this at creation or with a `PUT`:

```bash theme={null}
curl -X PUT https://api.prelude.dev/v2/session/apps/${APP_ID}/config/login/saml/okta/${CONNECTION_ID} \
  -H "Authorization: Bearer ${MANAGEMENT_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "behavior": {
      "jit_provisioning": true,
      "allow_email_account_merge": true,
      "email_domain_allowlist": ["acme.com"],
      "enforce_login": true,
      "default_redirect_uri": "https://app.acme.com/callback"
    }
  }'
```

After this, a user with an `@acme.com` email can no longer obtain a session via OTP or OAuth — only through this Okta connection.

## The `saml_login_required` error

When a client tries to start an OTP login for an enforced email, `POST /v1/session/otp` returns `403` with:

```json theme={null}
{
  "code": "saml_login_required",
  "type": "forbidden",
  "message": "This email domain must sign in with SSO. Please use SAML to continue."
}
```

This is the signal for your app to restart authentication through SAML. The Web SDK surfaces it as a typed `SAMLLoginRequiredError` so you can transparently fall back to `loginWithSAMLByEmail` — see [Enforce SSO login (Web SDK)](/session/documentation/frontend-sdks/web/saml-enforce).

## Notes and edge cases

* **Ambiguous or unmatched domains are not enforced.** Connection creation rejects overlapping allowlists, so a domain resolves to at most one connection; an unresolvable domain falls through to the normal login flows.
* **IdP-initiated logins are unaffected.** Enforcement only intercepts the OTP and OAuth entry points — a user clicking the IdP tile always lands on the ACS endpoint.
* **Existing sessions are not revoked.** Enforcement governs new logins; it does not invalidate sessions a user already holds.

## What's next?

<CardGroup cols={2}>
  <Card title="Enforce login (Web SDK)" icon="js" href="/session/documentation/frontend-sdks/web/saml-enforce">
    Handle <code>saml\_login\_required</code> and fall back to SAML in the browser.
  </Card>

  <Card title="SAML login (Web SDK)" icon="js" href="/session/documentation/frontend-sdks/web/saml">
    Start a standard SAML login from the JavaScript SDK.
  </Card>
</CardGroup>
