Skip to main content
The SSO button on the login page is driven by the OIDC configuration an administrator enters in Xpander Chat, 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 under Sign in with SSO. 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. xpander finds or creates the user by email in the in-cluster auth (GoTrue), then mints the session.
Keycloak sign-in page titled Sign in to your account with Username or email and Password fields

After the SSO button: the identity provider's own sign-in page, here Keycloak. Shown with sample data.

What to configure

In your identity provider

Create an OIDC client for xpander with: The pods need a routable path to the IdP. Step 4 above runs from inside the cluster, and node egress addresses change. An IdP whose public entry point is IP-restricted refuses the token and userinfo calls, even though the browser redirect worked. Give the pods their own path: a private-zone record and an internal load balancer entry for the IdP. The reference install publishes Keycloak as keycloak.internal.<domain> on an internal ingress class. Resolve that record from inside the cluster, so the issuer hostname still matches what the IdP advertises. For agent pre-authentication (the token-exchange option in Settings, OIDC), the Keycloak client needs more than the sign-in flow:
  • Standard token exchange enabled (Keycloak 26: the client attribute standard.token.exchange.enabled=true)
  • Direct access grants enabled
  • An audience client that represents the system agents call with the user’s own token, where that system allows it
  • A client scope carrying an audience mapper for that client
Leave the Agent pre-authentication switch on Settings > OIDC off until sign-in works. Then add the items above in your identity provider and turn the switch on. Keycloak and preferred_username. The login reads the user from Keycloak’s userinfo and expects preferred_username to carry the email. Keycloak sends the account username by default, so the auth service is asked to create a user called, for example, usman and refuses it (Unable to validate email address: invalid format). Fix it in Keycloak, on the client you created for xpander: Client scopes, the client’s dedicated scope, Add mapper, By configuration, User Property. Name preferred_username, Property email, Token Claim Name preferred_username, type String, “Add to userinfo” on. Then retry the SSO sign-in. Realm-wide alternative: Realm settings, Login, “Email as username”. Nothing changes on the xpander side; if the redirect round trip already works, your issuer, client and redirect URIs are correct. Releases with the September 2026 edge-functions image or later read email first and use preferred_username only when it is an address and email is absent, so on those releases the mapper is a safeguard rather than a requirement. Add it anyway; it works on every release.

In xpander (Settings, OIDC)

OIDC settings showing Sign-in, Sign-out and Initiate login redirect URIs, then an Agent pre-authentication card with the Enable agent pre-authentication switch on, one audience, two token exchange scopes and a Save configuration button

Settings > OIDC: the three redirect URIs and the Agent pre-authentication card with its switch, Audiences and Token exchange scopes. Shown with sample data.

In the chart

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

When it fails

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 with Keycloak

Group membership from the identity provider maps to xpander user groups. Four steps: two at the IdP, two in xpander.
  1. A client scope named groups. In Keycloak: Client scopes, Create client scope, name groups, type Optional. On that scope: Mappers, Add mapper, By configuration, Group Membership; name groups, Token Claim Name groups, Full group path off, Add to userinfo on. Then Clients, your xpander client, Client scopes, Add client scope, groups as Optional. A protocol mapper on the client’s dedicated scope alone is not enough: xpander requests scope=groups at sign-in, and Keycloak answers error=invalid_scope when no client scope of that name is assigned.
  2. Ask for the scope. Settings > OIDC > Additional sign-in scopes: click Add, type groups, then Save configuration. xpander records the claim name groups, the source userinfo and the extra scope groups for the organization.
  3. Sign in once. Each value the IdP sends in the groups claim is recorded for the organization, with when it was first and last seen.
  4. Link a group. Settings > User groups, the group row’s menu (three dots), Link SSO groups. The dialog, Link SSO groups to followed by the group name, lists the observed values behind a search box, plus Add a group value manually (name or ID) for one that has not been seen yet. Click Save links. The row then carries an SSO badge and reads “Linked to the Keycloak … group” with the linked value.
Members of the linked identity-provider groups join the xpander group at their next sign-in, as the dialog says.
OIDC settings with Issuer URL, Client ID, a saved Client secret, and groups entered under Additional sign-in scopes

Settings > OIDC with groups under Additional sign-in scopes. Shown with sample data.

User groups list with a row menu open showing Edit group, Customize features, Link SSO groups, Make default and Delete

Settings > User groups: the row menu with Link SSO groups.

Link SSO groups to Ops dialog reading 'Members of the linked identity-provider groups join this group at their next sign-in', with a search box, one observed group value, a manual entry field and a Save links button

Link SSO groups: the observed identity-provider values, a manual entry field and Save links. Shown with sample data.

A directory listing of groups (GET /oidc/<org-id>/groups) exists for Okta and Entra ID only. With Keycloak it answers 400; nothing depends on it, so the answer is harmless. A failed callback is silent. When the IdP answers the callback with an error, invalid_scope for example, the browser lands on / signed out and no message is shown. Read the error= parameter on the callback request in the browser’s network log, or the identity provider’s event log.

Running Keycloak yourself

If the identity provider is a Keycloak you install next to xpander, three server settings decide whether the discovery document works from inside the cluster:
  • --hostname=https://<keycloak host>/auth. A pathless hostname advertises endpoints the server then refuses.
  • --hostname-backchannel-dynamic=true, so pods can reach it on an internal name while browsers use the public one.
  • --proxy-headers=xforwarded, behind a load balancer.
Give the pod at least 2 GiB of memory when you administer it with kcadm.sh: each admin call starts a second JVM in the same cgroup. Install Keycloak only after its database, role and admin secret exist, or the release deadlocks waiting for a database that is not there.

Agent pre-authentication

Agent pre-authentication lets an agent act with the asking person’s own permissions where systems allow. At the moment of a call, xpander exchanges that person’s sign-in token (OIDC token exchange, RFC 8693) for a token scoped to a target system. At the IdP, the xpander client needs, beyond the sign-in flow:
  • Standard token exchange enabled. On Keycloak 26 that is the client attribute standard.token.exchange.enabled=true.
  • Direct access grants enabled.
  • An audience client that represents the system agents call with the person’s own token.
  • A client scope carrying an audience mapper for that client.
In xpander, open Settings > OIDC. The API service app card holds the exchange client’s Service app client ID and Service app client secret. The same card holds the Directory key used for key-pair directory reads. Further down, the Agent pre-authentication card has the Enable agent pre-authentication switch, Audiences (one per target system, for example acme-inventory) and Token exchange scopes (for example the audience’s scope and openid). Click Save configuration. Leave the switch off until plain sign-in works.