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

# SSO with your identity provider

> Sign in to an Air-Gapped xpander install with your own OIDC identity provider (Keycloak, Okta, Entra ID): what the SSO button uses, what to configure at the IdP, and what to check when it fails

The **SSO** button on the login page is driven by the OIDC configuration an administrator enters **in the app**, not by GoTrue's external providers. Google and GitHub social buttons are GoTrue providers (`supabase.auth.extraEnv` in the chart); SSO is a separate path, and this page is about that path.

## How a sign-in works

1. The user opens the login page, clicks **SSO** and types their work email. The email only picks the organization; it is never sent to the IdP.
2. The `client-auth` component builds the authorization URL from the organization's OIDC config and redirects the browser to your IdP. Scopes are `openid profile email` plus any extra scopes you add. The client is confidential: `client_id` and `client_secret`, no PKCE.
3. Your IdP redirects back to `https://<app-host>/oidc/<organization-id>/callback` with a code.
4. `client-auth` exchanges the code at the IdP's token endpoint and reads the user from the IdP's **userinfo** endpoint. Token and userinfo calls run from inside the cluster, so the IdP must be reachable from the pods, not only from browsers.
5. The platform finds or creates the user by email in the in-cluster auth (GoTrue), then mints the session.

## What to configure

### In your identity provider

Create an OIDC client for xpander with:

| Setting             | Value                                                                                                  |
| ------------------- | ------------------------------------------------------------------------------------------------------ |
| Client type         | Confidential (client authentication on)                                                                |
| Flow                | Authorization code (Keycloak: "Standard flow")                                                         |
| Valid redirect URIs | `https://<app-host>/oidc/*` (the exact URIs are listed in the app under Settings, OIDC, Redirect URIs) |
| Web origins         | `https://<app-host>`                                                                                   |
| Scopes              | The defaults (`openid`, `profile`, `email`). Add a `groups` scope only if you want group sync          |
| Claims              | `email` must be present in userinfo and must be the user's address                                     |

**Keycloak and `preferred_username`.** Keycloak's default mapper puts the account username into `preferred_username`. Releases before the September 2026 edge-functions image use that claim as the email when it differs from `email`, which fails user creation with `Unable to validate email address: invalid format`. On those releases, add a mapper on the xpander client: Client scopes, the client's dedicated scope, Add mapper, By configuration, **User Property**, with Name `preferred_username`, Property `email`, Token Claim Name `preferred_username`, type String, and "Add to userinfo" on. Realm-wide alternative: Realm settings, Login, "Email as username". Later releases use `email` whenever it is an address and treat `preferred_username` only as a fallback, so the mapper becomes optional.

### In xpander (Settings, OIDC)

| Field                     | Value                                                                                                                                                                                                                                                                  |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Issuer URL                | The realm or tenant issuer, exactly as in the IdP's discovery document, **including any context path**. A Keycloak serving under `/auth` is `https://idp.example.com/auth/realms/<realm>`, not the bare host. The platform appends `/.well-known/openid-configuration` |
| Client ID, Client secret  | From the client you created                                                                                                                                                                                                                                            |
| Additional sign-in scopes | Optional, for example `groups`                                                                                                                                                                                                                                         |
| Agent pre-authentication  | Optional token exchange for agents acting on the user's behalf; leave off to start                                                                                                                                                                                     |

### In the chart

```yaml theme={"dark"}
client-auth:
  env:
    APP_URL: https://<app-host>        # the UI origin, no trailing slash; it forms the redirect_uri
ui:
  authProviders: [sso]                 # renders the SSO button
```

`client-auth` is one of the published hosts (`client-auth.<domain>`); it also serves connector OAuth redirects.

## When it fails

| Symptom                                                                                                    | Cause                                                                                   | Fix                                                                                            |
| ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Browser never reaches the IdP                                                                              | Organization has no OIDC config, or the SSO feature is not in the license               | Fill Settings, OIDC; check the license features                                                |
| IdP shows an invalid redirect URI                                                                          | `APP_URL` differs from the UI origin, or the URI is not registered                      | Set `client-auth.env.APP_URL` to the exact UI origin; register `https://<app-host>/oidc/*`     |
| "Code exchange process failed" with `Unable to validate email address: invalid format` in the auth pod log | The email the IdP returned is not an address (Keycloak `preferred_username` case above) | Add the `preferred_username` mapper or upgrade the edge-functions image                        |
| "Code exchange process failed", auth pod log shows a token or userinfo error                               | The pods cannot reach the IdP, or the issuer is wrong                                   | Verify the issuer including its path; route the IdP through a load balancer the pods can reach |
| A different user is signed in than expected                                                                | Two profiles share an address in different organizations                                | Check the organization the email belongs to; the org-scoped match wins                         |

Logs to read: the `client-auth` pod (token and userinfo exchange), the `auth` (GoTrue) pod (user creation), and the `edge-runtime` pod (the `oidc-callback` function).

## Group sync and pre-authentication

Group membership from the IdP (`groups` claim or a directory lookup) can map to xpander user groups; agent pre-authentication lets agents obtain IdP tokens through token exchange. Both are configured in the same Settings, OIDC page and are optional. See the product updates for the current scope of group sync.
