Set up single sign-on (OIDC)
Point leancosts at your organization’s Keycloak — or any OIDC identity provider — so your team signs in with your IdP instead of magic-link email. SSO is configured per tenant, entirely through the app: no environment variables, no redeploy. It layers on top of magic-link, which stays as the bootstrap path until you choose to enforce SSO.
Everything below lives on Admin → Team → Single sign-on
(/admin?tab=team&sub=sso) and requires a tenant admin (the
governance.admin capability).
1. Register an OIDC client in your IdP
Section titled “1. Register an OIDC client in your IdP”In your identity provider (Keycloak realm, Entra ID, Okta, Auth0, …), create a confidential OIDC client for leancosts:
- Redirect / callback URI:
https://app.leancosts.com/api/auth/sso/callback/sso_<your-workspace-slug>(your workspace slug is the…/t/<slug>segment of your sign-in URL). If your IdP enforces exact matching and rejects the sign-in later, copy the exactredirect_urifrom that error and register it verbatim. - Scopes:
openid email profile. - Client authentication: on (confidential) — you’ll get a client ID and client secret.
- Make sure the realm’s discovery document
(
{issuer}/.well-known/openid-configuration) is publicly reachable from the internet — leancosts probes it directly.
2. Connect your realm
Section titled “2. Connect your realm”Open Admin → Team → Single sign-on and fill in the form:
| Field | What to enter |
|---|---|
| Display name | The label on the sign-in button, e.g. Acme SSO. |
| Client ID | The OIDC client you registered for leancosts. |
| Issuer URL | The realm base URL, e.g. https://kc.acme.com/realms/acme. Discovery runs against {issuer}/.well-known/openid-configuration. |
| Client secret | Paste the secret. It is write-only — stored encrypted and never shown again (you’ll see •••• and a Replace secret button on later edits). |
| Scopes | Optional; defaults to openid email profile. |
| Require verified email | On by default — rejects sign-ins where the IdP didn’t assert email_verified (account-takeover defense). |
Click Connect realm. The realm is saved in status Draft.
3. Test the connection
Section titled “3. Test the connection”Click Test connection. leancosts fetches your discovery document and reports
Discovery OK (with the authorize endpoint) or an actionable error. A successful
probe moves the realm to Tested.
4. Prove it with a real sign-in
Section titled “4. Prove it with a real sign-in”Sign in once through the IdP at your workspace URL /t/<your-slug> and pick
Continue with {your display name}. A successful round-trip flips the realm to
Proven and stamps the time. Only a proven realm can be enforced.
5. (Optional) Map IdP groups to roles
Section titled “5. (Optional) Map IdP groups to roles”By default, every new SSO user lands pending until an admin approves them. To auto-provision instead, expand Role mapping and turn on Map IdP groups to roles:
- Group claim — the OIDC claim carrying the user’s groups (Keycloak’s default
mapper emits
groups). - Group → role mappings — map a group value (e.g.
finops-admins) to a leancosts role. If a user is in several mapped groups, the highest-privilege match wins.
Save the mappings with Save changes (they save together with the realm).
6. Enforce SSO (optional)
Section titled “6. Enforce SSO (optional)”Once the realm is Proven, the Require SSO toggle unlocks. Turning it on disables magic-link for everyone in your tenant — they must sign in through your IdP.
After your team signs in
Section titled “After your team signs in”New SSO users (without a matching role mapping) arrive pending. Approve them on Admin → Team → Members — approving assigns the role you choose and grants access. Until then they can sign in but see no data.
Troubleshooting
Section titled “Troubleshooting”- “Could not reach the issuer” — the discovery URL isn’t publicly reachable,
or the issuer is wrong. Confirm
{issuer}/.well-known/openid-configurationloads from a browser. - Sign-in rejected with a
redirect_urierror — register the exact callback URL from the error in your IdP client (see step 1). - Sign-in rejected for unverified email — the IdP didn’t assert
email_verified. Verify the user’s email in your IdP, or (less safe) turn off Require verified email. - Can’t turn on Require SSO — the realm isn’t Proven yet. Complete one real SSO sign-in first.