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

# Enroll SAML SSO: Okta and Microsoft Entra ID

> Step-by-step enrollment of SAML 2.0 single sign-on for your Orbit organization — probing your IdP metadata, writing the config through Settings → Security, enforcing SSO, testing the login flow, rotating certificates, and the failures you're most likely to see.

# Enroll SAML SSO

Connect your identity provider so your team signs into the Orbit dashboard
through SAML 2.0 single sign-on: they authenticate at your IdP (Okta,
Microsoft Entra ID, or any SAML 2.0 IdP) and land in Orbit with a session —
no separate Orbit password. This guide walks the full enrollment: probing
your IdP metadata, writing the config, enforcing SSO, testing the flow, and
rotating certificates. For the endpoint spec, see the
[Settings API single sign-on table](/api-reference/settings); for the public
login endpoints, see [Authentication](/authentication).

SAML and SCIM are independent controls that pair well together: SAML controls
how users sign in, SCIM controls which users exist. Both are per-organization,
tenant-owned controls — nothing enables SAML except an owner in your
workspace, an organization without an IdP keeps password sign-in, and Orbit
does not require SSO for any compliance certification. It's your enforcement
option, not a mandate.

## Before you start

Collect these before touching Orbit:

1. An **owner** role in the Orbit organization. Creating and changing SAML
   configuration is owner-only; admins can read the config and probe metadata
   but cannot save changes.
2. Your IdP's **signing certificate** (X.509, PEM format). Okta shows it under
   the app's **Sign On** tab; Entra ID under the enterprise application's
   **Certificates** blade.
3. Your IdP's **SSO URL** and **entity ID**. If you don't have them handy,
   the metadata probe below can suggest them from a metadata URL.
4. An **attribute mapping plan** — decide which assertion attributes carry
   email and name (the canonical table below covers Okta and Entra defaults).
5. A decision on **enforcement**: will password sign-in stay available while
   you test, and when (if ever) do you flip to SSO-only? Plan a staging window
   before you enforce.

## Step 1 — Discover the IdP with a metadata probe

Most IdPs publish a metadata URL (Okta's "Identity Provider metadata" link,
Entra's "Federation metadata document") that describes the SSO endpoint and
entity ID. Probe it before you write anything, so you save known-good values:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/settings/saml/test-connection" \
  -H "Authorization: Bearer $SESSION_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "metadataUrl": "https://your-idp.example.com/federationmetadata.xml"
  }'
```

The probe fetches the URL (public https only — private, internal, or loopback
addresses are rejected), confirms it returns SAML metadata, and answers with:

* `ok` — `true` when the document fetched and looked like SAML metadata.
* `suggested_sso_url` — the URL from the `<SingleSignOnService>` element, when
  present.
* `suggested_entity_id` — the document's `entityID`, when present.

Use the suggestions directly in the next step. If your IdP has no metadata
URL, skip the probe and paste the SSO URL and entity ID from the IdP admin
console by hand.

## Step 2 — Write the SAML configuration

Save the config through the dashboard (**Settings → Security → Single
sign-on**) or the API. First-time setup must include `x509Cert`:

```bash theme={null}
curl -X PATCH "https://api.orbit.devotel.io/api/v1/settings/saml" \
  -H "Authorization: Bearer $SESSION_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "ssoUrl": "https://your-idp.example.com/app/orbit/sso/saml",
    "entityId": "http://www.okta.com/exkXXXXXXXXXXXXXXX",
    "x509Cert": "-----BEGIN CERTIFICATE-----\nMIIDpCC...IdP signing cert...\n-----END CERTIFICATE-----",
    "attrEmail": "mail",
    "attrName": "displayName",
    "enabled": true,
    "enforced": false
  }'
```

* `ssoUrl` — the IdP's sign-on endpoint (https, publicly resolvable).
* `entityId` — the IdP's issuer identifier (often a URN or URL).
* `x509Cert` — the PEM signing certificate. **Required on first setup; stored
  afterwards** — omit it on later PATCHes to keep the existing certificate
  while you change other fields.
* `attrEmail` / `attrName` — the assertion attribute names Orbit reads for the
  sign-in's email and display name (see the IdP table below).
* `enabled` — `true` turns SSO on. Keep `enforced: false` while you test.
* `enforced` — leave `false` until step 5.

The response echoes the saved config with `cert_fingerprint_sha256` — the raw
certificate is never returned. Compare the fingerprint to the one your IdP
renders to confirm you saved the right certificate. You can also preview a
certificate before saving with
`POST /api/v1/settings/saml/parse-certificate` (sends `{ "pem": "..." }`,
returns subject, issuer, and validity window without persisting anything).

### IdP attribute defaults

| Field | Okta default  | Microsoft Entra ID default |
| ----- | ------------- | -------------------------- |
| Email | `mail`        | `mail` or `emailaddress`   |
| Name  | `displayName` | `displayname`              |

Whatever you set must match what your IdP actually sends in the assertion. If
the IdP asserts a claim you haven't mapped (Okta stock templates assert
`mail`; Entra stock templates assert `displayname` and `emailaddress`, exact
casing depends on your app), wire the mapped names to match — a mismatch means
users authenticate at the IdP but the sign-in fails reading attributes.

## Step 3 — Point the IdP at Orbit

In your IdP admin console, add an Orbit application (or reuse the one you
provisioned for SCIM) with:

* **ACS URL (Assertion Consumer Service)**:
  `https://api.orbit.devotel.io/auth/saml/{orgSlug}/callback`
* **SSO entry point**:
  `https://api.orbit.devotel.io/auth/saml/{orgSlug}/login`
* **SP metadata** to import rather than type:
  `https://api.orbit.devotel.io/auth/saml/{orgSlug}/metadata`
* **Single logout URL**:
  `https://api.orbit.devotel.io/auth/saml/{orgSlug}/logout`

Find `{orgSlug}` under **Settings → Organization**. Every public SAML endpoint
is keyed by it.

## Step 4 — Test the flow end-to-end

With `enabled: true` and `enforced: false`, test as a non-owner member would:

1. Open `https://api.orbit.devotel.io/auth/saml/{orgSlug}/login` in a fresh
   browser profile (SP-initiated login — the standard path).
2. Orbit 302-redirects to your IdP. Authenticate as a test user assigned to
   the Orbit app.
3. The IdP POSTs the signed assertion to `/auth/saml/{orgSlug}/callback`; on
   success you land in the dashboard with a session.

Verify on the way through:

* **The email matches an Orbit member** — the assertion's mapped email
  attribute must match an existing member of the organization. If it doesn't,
  invite that person first.
* **Attribute mapping is the first failure to check** — a successful IdP
  login followed by an Orbit error almost always means the assertion's
  attribute names don't line up with `attrEmail` / `attrName`. Inspect the
  raw assertion in your IdP's SAML tracer and compare the claim names.
* **IdP-initiated login also works** — users clicking the Orbit tile inside
  Okta/Entra post directly to the ACS callback without the SP-initiated
  redirect. It works once the app is assigned; SP-initiated is still the
  recommended entry point because the slug determines the organization.

Confirm operation afterwards with `GET /api/v1/settings/saml/health` — it
reports the stored certificate's validity window (`cert.expiresInDays`, with
`cert.expired` once it lapses), the last successful SSO login, the last
failure with its reason, and the last metadata-test outcome.

## Step 5 — Enforce SSO

Once sign-in works for at least one other owner (so you are not locked out by
your own change), flip password login off:

```bash theme={null}
curl -X PATCH "https://api.orbit.devotel.io/api/v1/settings/saml" \
  -H "Authorization: Bearer $SESSION_JWT" \
  -H "Content-Type: application/json" \
  -d '{ "enforced": true }'
```

`enforced: true` disables password sign-in for every member of the
organization — everyone goes through SAML from then on. Keep a second owner
account verified on SSO before you flip this, and leave the change to an
owner deliberately staging it after testing.

## Rotate or revoke the certificate

IdP signing certificates expire. Rotate through the same PATCH endpoint with
a new `x509Cert`; the stored certificate is replaced and the fingerprint
changes. `POST /api/v1/settings/saml/parse-certificate` previews the new
cert's identity and validity window before you commit it, and
`GET /api/v1/settings/saml/health` reports `cert.expiresInDays` so you can
watch the deadline ahead of expiry.

Disabling SSO entirely is a PATCH with `enabled: false`: the configuration
stays stored (cert, URLs, mappings intact) and password sign-in becomes
available again. `enforced: false` loosens to "SSO offered alongside
passwords"; `enabled: false` turns SSO off altogether. To fully remove the
configuration, an owner clears it from **Settings → Security → Single
sign-on** — one more deliberate click, since a wrong lock-out decision puts
every member in front of an IdP that may be gone.

## Common failures

| Symptom                                                                                                                 | Cause                                         | Fix                                                                                                                               |
| ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `422` on `PATCH` with "x509Cert is required when configuring SAML for the first time"                                   | First setup sent without a certificate        | Include `x509Cert` on the first PATCH; later PATCHes can omit it.                                                                 |
| `400` on probe or PATCH — "`metadataUrl`/`ssoUrl` must use https\://" or "resolves to a private or internal IP address" | URL safety validation blocked the address     | Use a public https endpoint; no-loopback, no-internal-range, no cloud-metadata URLs make it through.                              |
| `403` on PATCH                                                                                                          | Role below owner                              | SAML configuration is owner-only; admins can read the config and run probes.                                                      |
| Probe returns `ok: false` with an HTTP status                                                                           | The IdP metadata URL is wrong or gated        | Re-copy the metadata URL from the IdP; the metadata endpoint has to be reachable from the public internet.                        |
| Probe returns `ok: false` with `invalid metadata`                                                                       | URL reachable but not SAML metadata           | Point it at the actual IdP metadata document, not a discovery or login page.                                                      |
| IdP login succeeds, Orbit callback fails                                                                                | Attribute-mapping mismatch                    | Compare the assertion's claim names to `attrEmail` / `attrName` — the table above covers Okta and Entra defaults.                 |
| Members locked out after enforcement                                                                                    | Enforce flipped before any owner verified SSO | The remaining owner opens the dashboard with SSO, sets `enforced: false` to re-open password sign-in, then re-stages enforcement. |

## See also

* [Wire SCIM provisioning](/guides/scim-provisioning) — sync membership and
  roles from the same IdP; the attribute table and token flow mirror this
  guide.
* [Settings → Security console](/guides/settings-security-console) — the
  dashboard walkthrough of the same surface this guide covers by API.
* [Authenticate Orbit API requests](/authentication) — the public SAML login
  endpoints and the API key/session distinction.
* [Settings API](/api-reference/settings) — the full single sign-on endpoint
  table (GET / PATCH / test-connection).
* [Troubleshooting: authentication gates](/troubleshooting/auth-and-api-keys)
  — every SSO rejection code and its fix.
