# Single sign-on

> Register this platform at your identity provider, connect it, map groups to roles, test discovery, what login looks like afterwards, and why SSO can never mint a tenant admin.

_How-to · Updated 2026-10-17 · Describes release 0.1.164_

The **Single sign-on** card on **Settings · Account → Members & roles** connects one OIDC identity provider to your workspace, so your users sign in with their work email and never hold a password on this platform. This page is the whole procedure, provider side and platform side. Inviting people by link instead is on [Members and roles](/help/members-and-roles.md#invite-a-member); the roles a group can map to are in the [Permissions](/help/permissions.md) reference.

## Per-tenant SSO {#per-tenant-sso}

One OIDC issuer per tenant. Your users then sign in with their work email and never hold a password on this platform.

### First, register this platform at your provider

SSO is an exchange: you paste your provider's details here, and your provider has to be told about this platform. Doing only the first half is the usual first failure — the sign-in gets as far as your provider and stops with *redirect_uri did not match*.

At your identity provider, create an **OIDC confidential client** (not SAML — this platform speaks OIDC only) and register these two URLs on it. Both are shown with copy buttons at the top of the **Single sign-on** card, filled in with this deployment's real address:

| What to register | Value | Notes |
| --- | --- | --- |
| Redirect URI / callback URL | `https://<your-deployment>/oidc/callback` | Required. Must match exactly, `https` included, no trailing slash added. |
| Post-logout redirect URI | `https://<your-deployment>/login` | Optional. Without it, signing out here leaves your provider's session alive. |

The same callback serves every workspace on the deployment — tenants are told apart by their issuer, not by a per-tenant URL. Each vendor labels the field differently: **Entra ID** calls it a *Redirect URI* under platform type **Web**, **Okta** a *Sign-in redirect URI*, **Keycloak** a *Valid redirect URI*, **Auth0** an *Allowed Callback URL*.

Then copy the client id and secret it gives you, and the issuer:

| Provider | Issuer URL |
| --- | --- |
| Microsoft Entra ID | `https://login.microsoftonline.com/<tenant-guid>/v2.0` |
| Okta | `https://<org>.okta.com/oauth2/default` |
| Google Workspace | `https://accounts.google.com` |
| Auth0 | `https://<tenant>.auth0.com/` |
| Keycloak | `https://<host>/realms/<realm>` |

Paste the issuer itself, never its discovery document — drop any trailing `/.well-known/openid-configuration`. **Test discovery** on the card fetches that document for you and reports what it found.

Entra ID needs one more thing: tick **Trust email from this provider**. It does not send `email_verified`, and without that box its logins are rejected as unverified.

### Connect an identity provider

Create any custom roles you intend to map **before** saving the provider — the save validates that every role it references already exists in this tenant.

1. Open **Settings · Account → Members & roles → Single sign-on**. Requires `members.manage`.
2. Fill **Issuer URL** — the OIDC issuer from the table above. It must be an `http(s)` URL.
3. Fill **Provider name** — the label your users see, default `Company SSO`.
4. Fill **Client id** and **Client secret** for the confidential client you registered.
5. Fill **Email domains (comma-separated)**, for example `example.com`. At least one is required, each must look like a real domain, and a domain belongs to exactly one identity provider platform-wide.
6. Choose **Default role for new logins** — start with `viewer`.
7. Leave **Groups claim** as `groups` unless your identity provider names it differently.
8. Fill **Group → role map (one per line: group = role)**, for example `sre-team = operator`. See *Map identity-provider groups to roles* for the caveat on more than one mapping.
9. Tick **Auto-provision members on first login** to create accounts and memberships automatically, and **Enabled**.
10. Click **Save identity provider**.

The card header grows an `enabled` status badge and a `secret stored` badge, the notice reads `Identity provider saved.`, and a **Test discovery** button and a **Remove** button appear. The save is audited as `idp.updated` with the issuer and domain list.

Field notes: the client secret is write-only. Once stored, the label reads `(stored — blank keeps it)`; leaving it blank keeps the current value, typing a value rotates it, and it is never rendered back to the browser. Where that secret physically lives and how it is encrypted is **Security and data** → *The tenant secret store*.

### Map identity-provider groups to roles

The **Group → role map** takes one `group = role` per line. Save reports the exact line when a mapping is malformed, duplicated or names an unavailable SSO role; it never silently drops that line. Default-role choices and mappings exclude roles carrying `members.manage`; the server remains authoritative.

1. Create every custom role you will map. Requires `members.manage`.
2. Type the mappings, one per line, and save.

A group that maps to no role falls back to **Default role for new logins**. One mapping can never grant `members.manage`, no matter which role it names — see the note below.

To script it instead:

1. Mint a write-scope API token on **Alert intake & tokens** (`/portal/tokens`) — see **Integrations**.
2. Find your tenant id: `GET /api/aiops/me` returns one entry per membership with its `tenant_id`.
3. `PUT` the whole provider, including the full map. Omitting `client_secret` keeps the stored one.

```bash
curl -s -X PUT -H "Authorization: Bearer $AIOPS_TOKEN" \
  -H "Content-Type: application/json" \
  "https://aiops.example.com/api/aiops/$TENANT_ID/idp" \
  -d '{"issuer":"https://login.example.com/realms/example",
       "client_id":"aiops",
       "provider_name":"Company SSO",
       "email_domains":["example.com"],
       "default_role":"viewer",
       "groups_claim":"groups",
       "group_role_map":{"sre-team":"operator","finance":"executive"},
       "auto_provision":true,"enabled":true}'
```

The response echoes the stored provider with the full `group_role_map` and `has_secret: true`, and the change lands as another `idp.updated` row. Re-open the **Single sign-on** card and confirm before relying on it: if the map is right in the response but the field looks collapsed, do not press **Save identity provider** again.

### Test discovery

1. With the provider saved, click **Test discovery**. Requires `members.manage`. The button only appears once a provider exists.
2. Read the notice under the form.

A working issuer returns `Discovery OK — <authorization endpoint>`. A broken one returns the error text; the button never fails with a server error. The probe fetches `<issuer>/.well-known/openid-configuration` through the same SSRF guard as datasource probes, so an issuer on a private address is reported as rejected rather than silently attempted.

### What login looks like afterwards

The sign-in page grows a **Company SSO — enter your work email** box as soon as any enabled tenant provider exists, anywhere on the deployment. A user types their address and clicks **Continue**; the platform matches the email domain to your provider and redirects straight into it. A domain nobody claims returns `No company SSO is configured for this email domain.`

On return, the member is created with the role from the first matching group in **Group → role map** (group names are evaluated in sorted order for determinism), otherwise **Default role for new logins**. Existing members are never re-roled by a login — a role you assigned by hand always wins.

Your provider must assert `email_verified` for the address. Unlike the platform's own issuer, a tenant provider has no relax toggle: an unverified email is refused with `sso email not verified`, and a provider may only assert addresses inside the domains it claims (`sso email domain not allowed for this provider`).

Signing out asks your identity provider to end its session too (RP-initiated logout), so the next login is a real login. If your provider publishes no `end_session_endpoint`, or discovery fails, the platform falls back to ending the local session only.

Removing the provider with **Remove identity provider** stops SSO logins for those domains immediately; existing accounts and memberships survive. The removal is audited as `idp.removed`.

### SSO can never mint a tenant admin

A role reachable through SSO can never carry `members.manage`. This is enforced three times: when the identity provider is saved, when a role referenced by it is created or edited, and again at login as a backstop. Saving a provider that points at such a role returns `role '<name>' carries members.manage — not assignable via SSO`; going the other way — adding `members.manage` to a role the provider already references — returns `role '<name>' is assigned via SSO — it cannot carry members.manage`. Tenant administration is always granted by a human already inside the tenant, never by a directory group.

Two related refusals: platform-staff email domains are reserved and cannot be claimed, and a customer-controlled provider can never auto-link onto a superadmin account, a local-password account, or an account already belonging to another tenant (`account exists — ask a tenant owner to invite you`). An account minted by any login method other than OIDC is refused as well.

Hosted-realm provisioning — the platform creating a dedicated Keycloak realm for you — is a platform-staff action, not something this card can do. Ask your account contact; the procedure is **Customers and workspaces** → *Provision a Keycloak realm* in the platform console handbook. It lands here as an `idp.provisioned` audit row.

## Troubleshooting

Cross-cutting symptoms — a queued investigation, a missing page, an empty approval queue — are in **Troubleshooting**. The table below covers only the errors the **Single sign-on** card and an SSO login produce.

| Symptom | Cause | Fix |
| --- | --- | --- |
| `role '<name>' carries members.manage — not assignable via SSO` | Saving an identity provider that maps to an admin role | Map the group to a role without `members.manage` and grant admin by hand |
| `role '<name>' is assigned via SSO — it cannot carry members.manage` | Adding `members.manage` to a role the provider already references | Remove it from the provider's default role / group map first, or use a different role |
| `issuer must be an http(s) URL` / `client_id required` | Required identity-provider fields malformed or blank | Paste the issuer exactly as your provider publishes it, without a trailing path typo |
| `at least one email domain required` / `invalid email domain '<d>'` | Domain list empty or not a domain | Comma-separate bare domains, for example `example.com, example.net` |
| `email domain '<d>' is already claimed by another identity provider` | Domains are globally unique | Use a domain only your organisation owns, or contact your account contact |
| `email domain '<d>' is reserved for platform staff` | Staff domains cannot be claimed | Remove that domain from the list |
| **Test discovery** returns an error instead of `Discovery OK` | Issuer unreachable, wrong URL, or blocked by the SSRF guard | Verify the issuer serves `/.well-known/openid-configuration` from a reachable address |
| `sso email not verified` | Your provider did not assert `email_verified` | Enable verified-email claims; tenant providers have no relax toggle |
| `sso email domain not allowed for this provider` | The asserted address is outside the claimed domains | Add the domain to **Email domains**, or fix the account's address |
| `account not provisioned` on first SSO login | **Auto-provision members on first login** is off | Tick it, or add the member by email first |
| `account exists — ask a tenant owner to invite you` | The address matches a staff, password or other-tenant account | Add that person as a member by email instead of relying on auto-link |
| SSO login lands with the wrong role | A membership already existed, or no group matched | Manual assignment always wins — change the role on the Members card |
| Users stopped being able to sign in with SSO | The provider was removed or **Enabled** was unticked | Re-save it; local accounts are unaffected |

## Limits and gotchas

- **One identity provider per tenant**, and one owner of each email domain platform-wide.
- **Keycloak realm provisioning is platform-staff only.** The **Single sign-on** card connects a provider you already run; a hosted realm is arranged through your account contact.


## Next steps

- Next: [Support access](/help/support-access.md)
- [All Administer a workspace pages](/help/administer-a-workspace/llms.txt)
- [Documentation home](/help/)
