Files
sencho/docs/features/sso.mdx
T
Anso ce08a593d7 feat(settings): reorganize the settings hub into domain groups (#1321)
* refactor(settings): split System Limits and regroup the hub

System Limits had grown into a grab-bag of host alert thresholds, Docker
cleanup, and mesh data-plane controls under one mislabeled section. Split it
into Host Alerts, Docker & Storage, and Fleet Mesh, and split Developer into
Developer Diagnostics and Data Retention. Reorganize the sidebar into ten
domain groups: Personal, Access, Infrastructure, Monitoring, Notifications,
Automation, Organization, Security, Operations, Help.

Each section now saves only its own keys, so a concurrent edit in one section
no longer clobbers another. Data Retention sends the audit-log window only on
a paid plan, matching the field's existing visibility, so a Community save no
longer fails on a key the operator cannot set. NumberChip moves to a shared
module and the toggle reuses the existing shared component. The /settings API
is unchanged.

* test(settings): cover registry structure and per-section save payloads

Add structural invariants for the ten-group registry (every item maps to a
real group, ids are unique, the System Limits and Developer splits land in the
right groups with the right gates, renamed labels and the Registries paid gate
hold) and per-section payload tests asserting each split section patches only
its own keys, including the Community path where Data Retention omits the paid
audit-log key.

* docs(settings): document the regrouped settings hub

Rewrite the settings reference for the ten-group layout, replace the System
Limits page with Host Alerts, Docker & Storage, and Fleet Mesh, and document
the prune-on-update, reclaimable-space banner, and mesh auto-recreate settings
that were previously undocumented. Update the Settings navigation breadcrumbs
across the feature docs and refresh the affected screenshots.

* fix(settings): show Access sections as instance-global, not operator-scoped

License, Users, SSO, and API Tokens are instance-global settings but the
masthead scope label rendered them as operator-scoped because it keyed off the
old Identity group. Only Personal sections (account, appearance) are
operator/browser-scoped now; everything else reads as global.

Also add a compile-time exhaustiveness guard to the section switch so a future
SectionId added without a matching case fails the build instead of silently
rendering a blank panel.

* docs(settings): remap remaining settings breadcrumbs to the new groups

Update the navigation breadcrumbs that still pointed at the removed Identity,
Alerts, and Advanced groups: API Tokens and Users now sit under Access, Webhooks
under Automation, Labels under Organization, App Store under Infrastructure,
Appearance under Personal, and scan policies under Security > Vulnerability
Scanning. Correct the settings reference scope note so Access reads as global.

* docs(settings): remap renamed-section breadcrumbs across feature docs

Sweep every feature, operations, getting-started, and reference page for
navigation paths that still named the renamed settings sections, and point them
at the current ones: Security becomes Security > Vulnerability Scanning,
Notifications becomes Notifications > Channels, Routing becomes Notifications >
Notification Routing, and Developer becomes Operations > Developer Diagnostics
(with its retention windows under Operations > Data Retention). App Store moves
under Infrastructure and the four-group overview in the getting-started intro is
rewritten to the ten groups. Separators each page already used are preserved.
2026-06-05 23:01:37 -04:00

321 lines
19 KiB
Plaintext

---
title: SSO & LDAP Authentication
description: Authenticate with your existing identity provider, including LDAP, Google, GitHub, Okta, and any spec-compliant OIDC provider.
---
Sencho lets your team sign in with the identity provider you already use instead of maintaining a second set of credentials. SSO works **alongside** password authentication; it does not replace it.
SSO is available on every Sencho tier. Custom OIDC and the preset providers for Google, GitHub, and Okta work on every tier; LDAP and Active Directory require Sencho Admiral.
## Supported providers
| Provider | Protocol | Tier | Notes |
|----------|----------|------|-------|
| **Custom OIDC** | OpenID Connect | Community | Any spec-compliant OIDC provider: Authelia, Keycloak, Authentik, Zitadel, KanIDM, Pocket ID, and more |
| **Google** | OpenID Connect | Community | Preset for Google Workspace or personal Google accounts, with branded login button |
| **GitHub** | OAuth 2.0 | Community | Preset for GitHub personal accounts and GitHub orgs, with branded login button |
| **Okta** | OpenID Connect | Community | Preset for any Okta org or Okta-compatible IdP, with branded login button |
| **LDAP / Active Directory** | LDAP bind + search | Admiral | Works with OpenLDAP, Active Directory, FreeIPA, and any LDAPv3 server |
## How it works
### LDAP flow
1. User enters their directory username and password on the Sencho login page.
2. Sencho binds to LDAP with a service account, locates the user, then verifies their password.
3. On the user's first login, a Sencho account is created automatically.
4. Sencho issues a session JWT and the user is logged in, identical to a password login.
### OIDC / OAuth flow (Google, GitHub, Okta, Custom OIDC)
1. User clicks the provider button on the login page (e.g., **Sign in with Google**).
2. The browser is redirected to the identity provider for authentication.
3. After granting consent, the provider redirects back to Sencho with an authorization code.
4. Sencho exchanges the code for tokens, verifies the ID token, and reads user information.
5. On the user's first login, a Sencho account is created automatically.
6. Sencho issues a session JWT and the user lands on the dashboard.
## Auto-provisioning
When a user signs in via SSO for the first time, Sencho creates a local account:
- **Username** is derived from the identity provider profile (display name, email prefix, or login handle).
- **Role** is assigned from [role mapping](#role-mapping); defaults to Viewer if no mapping matches.
- **Password** is set to an unusable placeholder. SSO users cannot sign in with the password form.
On every subsequent sign-in, the existing account is reused and the user's **email** and **role** are synced from the identity provider. Adding someone to your admin group promotes them to Admin on their next sign-in; removing them demotes them to the default role.
## Role mapping
### LDAP group mapping
Set the **Admin Group DN** to a group in your directory. Members of that group receive the Admin role; everyone else receives the default role (Viewer).
Example: if your admin group is `cn=sencho-admins,ou=groups,dc=example,dc=com`, set that as the **Admin Group DN**. Users whose `memberOf` attribute lists that DN are provisioned as Admin.
### OIDC claim mapping
For OIDC providers, configure two fields:
| Field | Description | Example |
|-------|-------------|---------|
| **Admin Claim** | The token claim name that contains role information | `groups` |
| **Admin Claim Value** | The value within that claim that grants Admin | `sencho-admins` |
If the user's ID token contains a `groups` claim with the value `sencho-admins`, they receive Admin. Otherwise, they receive the default role.
<Note>
Not every provider includes a `groups` claim by default. You may need to configure custom claims or scopes in your identity provider's admin console to surface group membership in the ID token.
</Note>
## Configuration
SSO can be configured two ways:
1. **Settings UI**: go to **Settings → SSO** in the Sencho dashboard. Enable providers, paste credentials, and test connections from the UI. Changes take effect immediately, no restart required.
2. **Environment variables**: set `SSO_*` variables in your Docker Compose file. They seed the database on first boot; afterwards the Settings UI is authoritative.
### Via Settings UI
Admins manage SSO providers in **Settings → SSO**. The page lists every provider as a collapsible card with a label, an **enable / disable** toggle pill on the right, and an **Active** badge on the header when the provider is on.
<Frame>
<img src="/images/sso/sso-settings.png" alt="SSO settings panel listing the five identity providers as collapsible cards with enable / disable toggles" />
</Frame>
Click a card to expand it. The footer of every expanded form has the same actions:
- **Save**: persists changes for that provider.
- **Test Connection**: runs a live check (LDAP bind for LDAP, OIDC discovery for the OIDC providers) and shows a green check or red X next to the button with the result.
- **Remove**: clears the saved configuration (only present once a config has been saved at least once).
A static helper sits below all five cards. It reminds you that SSO users are auto-provisioned on first login and shows the OAuth callback URL template for OIDC providers:
```
https://<your-sencho-url>/api/auth/sso/oidc/<provider>/callback
```
### LDAP fields
| Field | Description |
|-------|-------------|
| **Server URL** | LDAP server URL (e.g., `ldap://ldap.example.com:389` or `ldaps://...` for TLS) |
| **Bind DN** | Service account DN used to search the directory |
| **Bind Password** | Service account password |
| **Search Base** | Base DN for user searches (e.g., `ou=users,dc=example,dc=com`) |
| **Search Filter** | LDAP filter template using `{{username}}` as a placeholder |
| **Admin Group DN** | DN of the group whose members receive the Admin role |
| **Default Role** | Role assigned to users not in the admin group (Viewer or Admin) |
| **Verify TLS certificate** | Toggle to enable or disable TLS certificate verification |
For Active Directory, set **Search Filter** to `(sAMAccountName={{username}})`. The form's helper text shows the same example inline.
<Frame>
<img src="/images/sso/sso-settings-ldap.png" alt="LDAP / Active Directory configuration form with server URL, bind DN, search base, role mapping, and Verify TLS toggle" />
</Frame>
### OIDC fields (Google, GitHub, Okta)
| Field | Description |
|-------|-------------|
| **Issuer URL** | (Okta only) Your Okta org issuer URL, e.g., `https://dev-123456.okta.com` |
| **Client ID** | OAuth client ID from your identity provider |
| **Client Secret** | OAuth client secret |
| **Admin Claim** | Token claim name inspected for role mapping (default: `groups`) |
| **Admin Claim Value** | Value within the claim that grants Admin (default: `sencho-admins`) |
| **Scopes** | Space-separated OAuth scopes (default: `openid email profile`). Customize if your provider needs additional scopes to emit group claims. |
| **Default Role** | Role assigned when no claim mapping matches (Viewer or Admin) |
Google and GitHub already know their own issuer URL, so the form omits that field for those providers.
<Frame>
<img src="/images/sso/sso-settings-oidc.png" alt="Google OIDC configuration form with client ID, client secret, admin claim mapping, scopes, and default role" />
</Frame>
### Custom OIDC fields
The **Custom OIDC** form adds the fields needed to point Sencho at a self-hosted or third-party identity provider:
| Field | Description |
|-------|-------------|
| **Display Name** | Label shown on the login button (e.g., `Corporate SSO`) |
| **Issuer URL** | Base URL of the OIDC discovery endpoint (without `/.well-known/openid-configuration`) |
| **User ID Claim** | Claim used as the unique user identifier (default: `sub`) |
| **Username Claim** | Claim used for the display name (default: `preferred_username`) |
| **Email Claim** | Claim used for the email address (default: `email`) |
Leave the three claim fields blank to use the standard OIDC defaults. Most spec-compliant providers will work out of the box; override only when your provider emits non-standard claim names.
<Frame>
<img src="/images/sso/sso-settings-custom-oidc.png" alt="Custom OIDC configuration form with display name, issuer URL, claim mapping fields, scopes, and default role" />
</Frame>
<Note>
The **User ID Claim**, **Username Claim**, and **Email Claim** fields are also accepted on Google, GitHub, and Okta as environment variables (see [Custom OIDC env vars](#custom-oidc)). The Settings UI hides them on the presets because the defaults match those providers; reach for them only if you have a custom claim layout to map.
</Note>
### Via environment variables
Environment variables are useful for initial deployment and infrastructure-as-code workflows. They seed the SSO configuration on first startup. After that, changes made in the Settings UI take precedence.
## SSO environment variables reference
### LDAP
| Variable | Default | Description |
|----------|---------|-------------|
| `SSO_LDAP_ENABLED` | `false` | Enable LDAP authentication |
| `SSO_LDAP_DISPLAY_NAME` | `LDAP` | Label shown on the login button (e.g., `Corporate AD`) |
| `SSO_LDAP_URL` | - | LDAP server URL (e.g., `ldap://ldap.example.com:389` or `ldaps://ldap.example.com:636`) |
| `SSO_LDAP_BIND_DN` | - | Service account DN for searching users |
| `SSO_LDAP_BIND_PASSWORD` | - | Service account password (encrypted at rest in the database) |
| `SSO_LDAP_SEARCH_BASE` | - | Base DN for user searches (e.g., `ou=users,dc=example,dc=com`) |
| `SSO_LDAP_SEARCH_FILTER` | `(uid={{username}})` | LDAP filter template. Use `(sAMAccountName={{username}})` for Active Directory |
| `SSO_LDAP_ADMIN_GROUP_DN` | - | DN of the group whose members receive the Admin role |
| `SSO_LDAP_DEFAULT_ROLE` | `viewer` | Role assigned to LDAP users not in the admin group |
| `SSO_LDAP_TLS_REJECT_UNAUTHORIZED` | `true` | Set to the literal string `false` to skip TLS certificate verification (useful for self-signed certs in development) |
### Google OIDC
| Variable | Default | Description |
|----------|---------|-------------|
| `SSO_OIDC_GOOGLE_ENABLED` | `false` | Enable Google SSO |
| `SSO_OIDC_GOOGLE_CLIENT_ID` | - | OAuth client ID from Google Cloud Console |
| `SSO_OIDC_GOOGLE_CLIENT_SECRET` | - | OAuth client secret (encrypted at rest) |
### GitHub OAuth
| Variable | Default | Description |
|----------|---------|-------------|
| `SSO_OIDC_GITHUB_ENABLED` | `false` | Enable GitHub SSO |
| `SSO_OIDC_GITHUB_CLIENT_ID` | - | OAuth app client ID from GitHub Developer Settings |
| `SSO_OIDC_GITHUB_CLIENT_SECRET` | - | OAuth app client secret (encrypted at rest) |
### Okta OIDC
| Variable | Default | Description |
|----------|---------|-------------|
| `SSO_OIDC_OKTA_ENABLED` | `false` | Enable Okta SSO |
| `SSO_OIDC_OKTA_ISSUER_URL` | - | Okta issuer URL (e.g., `https://dev-123456.okta.com`) |
| `SSO_OIDC_OKTA_CLIENT_ID` | - | Okta application client ID |
| `SSO_OIDC_OKTA_CLIENT_SECRET` | - | Okta client secret (encrypted at rest) |
### Custom OIDC
| Variable | Default | Description |
|----------|---------|-------------|
| `SSO_OIDC_CUSTOM_ENABLED` | `false` | Enable the custom OIDC provider |
| `SSO_OIDC_CUSTOM_DISPLAY_NAME` | `Custom OIDC` | Label shown on the login button |
| `SSO_OIDC_CUSTOM_ISSUER_URL` | - | OIDC issuer URL (the base of the discovery endpoint) |
| `SSO_OIDC_CUSTOM_CLIENT_ID` | - | OAuth client ID from your identity provider |
| `SSO_OIDC_CUSTOM_CLIENT_SECRET` | - | OAuth client secret (encrypted at rest) |
| `SSO_OIDC_CUSTOM_SCOPES` | `openid email profile` | Space-separated OAuth scopes |
| `SSO_OIDC_CUSTOM_ID_CLAIM` | `sub` | Token claim for the unique user identifier |
| `SSO_OIDC_CUSTOM_USERNAME_CLAIM` | `preferred_username` | Token claim for the display name |
| `SSO_OIDC_CUSTOM_EMAIL_CLAIM` | `email` | Token claim for the email address |
The `*_ID_CLAIM`, `*_USERNAME_CLAIM`, and `*_EMAIL_CLAIM` variables are also accepted for Google, GitHub, and Okta (substitute the provider prefix, e.g., `SSO_OIDC_OKTA_USERNAME_CLAIM`). They override the per-provider defaults when your token layout differs.
### General
| Variable | Default | Description |
|----------|---------|-------------|
| `SSO_OIDC_ADMIN_CLAIM` | `groups` | Token claim name inspected for Admin role mapping |
| `SSO_OIDC_ADMIN_CLAIM_VALUE` | `sencho-admins` | Value in the admin claim that maps to the Admin role |
| `SSO_DEFAULT_ROLE` | `viewer` | Default role for all SSO users when no mapping matches |
| `SSO_CALLBACK_URL` | auto-detect | External base URL for OAuth callback URLs (see below) |
## Reverse proxy and callback URLs
<Warning>
If Sencho is behind a reverse proxy (nginx, Traefik, Caddy), you **must** set `SSO_CALLBACK_URL` to your external URL. Otherwise, OAuth callbacks will fail.
</Warning>
Set `SSO_CALLBACK_URL` to the URL users use to reach Sencho, for example, `https://sencho.example.com`. Sencho uses this to construct the OAuth redirect URI that your identity provider calls back to.
If not set, Sencho auto-detects the URL from the request's `Host` header and protocol, which works for direct access but fails behind proxies that rewrite the host.
## Provider-specific setup examples
### Keycloak
1. Create a new client in your Keycloak realm (Client type: **OpenID Connect**).
2. Set **Valid redirect URIs** to `https://sencho.example.com/api/auth/sso/oidc/oidc_custom/callback`.
3. Enable **Client authentication** (confidential access type) and copy the client secret from the **Credentials** tab.
4. The Issuer URL is your realm URL: `https://keycloak.example.com/realms/myrealm`.
5. Keycloak uses standard claim names by default, so claim mapping can be left blank.
### Authentik
1. Create a new **OAuth2 / OpenID Provider** in Authentik.
2. Set the redirect URI to `https://sencho.example.com/api/auth/sso/oidc/oidc_custom/callback`.
3. Copy the Client ID and Client Secret.
4. The Issuer URL is `https://authentik.example.com/application/o/<slug>/`.
5. Standard claims work. For group-based admin mapping, configure a `groups` scope in Authentik so it emits the claim into the ID token.
### Authelia
1. Add an OpenID Connect client to your Authelia configuration under `identity_providers.oidc.clients`.
2. Set `redirect_uris` to include `https://sencho.example.com/api/auth/sso/oidc/oidc_custom/callback`.
3. The Issuer URL is your Authelia domain: `https://auth.example.com`.
4. Authelia uses standard OIDC claims.
### Zitadel
1. Create a new **Web** application in your Zitadel project.
2. Add `https://sencho.example.com/api/auth/sso/oidc/oidc_custom/callback` as a redirect URI.
3. The Issuer URL is your Zitadel instance URL: `https://zitadel.example.com`.
4. Copy the Client ID and Client Secret from the application settings.
### KanIDM
1. Create a new OAuth2 client in KanIDM (`kanidm system oauth2 create ...`).
2. Add the redirect URL with `kanidm system oauth2 add-redirect-url ...` and fetch the basic secret with `kanidm system oauth2 show-basic-secret ...`.
3. The Issuer URL is `https://kanidm.example.com/oauth2/openid/<client_id>`.
4. KanIDM may emit `name` instead of `preferred_username` for the username claim. If usernames look wrong after the first login, set **Username Claim** to `name`.
### Pocket ID
1. Create a new OIDC client in Pocket ID.
2. Set the callback URL to `https://sencho.example.com/api/auth/sso/oidc/oidc_custom/callback`.
3. The Issuer URL is your Pocket ID instance URL.
4. Copy the Client ID and Client Secret from the client view (Pocket ID shows the secret only at creation; reset it from the same screen if you lose it).
## Security
- **PKCE**: All OIDC flows use `code_challenge_method=S256` to prevent authorization code interception.
- **State parameter**: A cryptographic random value protects against CSRF on the OAuth callback.
- **Encrypted secrets**: LDAP bind passwords and OIDC client secrets are encrypted at rest in the Sencho database.
- **No local password**: SSO users are created with an unusable password hash and cannot bypass SSO by using the password sign-in form.
- **Admin-only configuration**: Only administrators can enable or configure SSO providers.
## Troubleshooting
<AccordionGroup>
<Accordion title="Test Connection returns 'Discovery failed' or a network timeout">
The Sencho container could not reach the provider's discovery URL. Verify the **Issuer URL** is reachable from inside the container (not just from your browser), confirm it does not include `/.well-known/openid-configuration` (just the base issuer URL), and check that container DNS can resolve the hostname. For providers with HTTPS, make sure the certificate chain is valid; self-signed certs may need additional container configuration.
</Accordion>
<Accordion title="Sign-in fails with an issuer validation error">
The `issuer` value in the provider's discovery document does not match what Sencho expects. This commonly happens when the **Issuer URL** has a trailing-slash mismatch (e.g., `https://auth.example.com` vs `https://auth.example.com/`), or when the provider is accessed via a different hostname than it advertises in its discovery document. Fix: set the **Issuer URL** to exactly match the `issuer` field returned by your provider's `/.well-known/openid-configuration` endpoint.
</Accordion>
<Accordion title="Users land with the wrong username or no email">
Enable **Developer Mode** (Settings → Operations → Developer Diagnostics) to log the raw claims Sencho receives from the provider in the server logs. Check your provider's documentation for which claims it includes in the ID token and `userinfo` response, and verify that the configured **Scopes** include everything your provider needs to emit `email` and group claims. For Custom OIDC, override **User ID Claim**, **Username Claim**, or **Email Claim** to match the names your provider actually emits.
</Accordion>
<Accordion title="The provider returns 'invalid redirect URI' during sign-in">
The callback URL registered with your identity provider must exactly match `https://sencho.example.com/api/auth/sso/oidc/<provider>/callback`, where `<provider>` is `oidc_google`, `oidc_github`, `oidc_okta`, or `oidc_custom`. If Sencho sits behind a reverse proxy, set `SSO_CALLBACK_URL` to your external URL. Some providers are strict about trailing slashes and HTTP vs HTTPS.
</Accordion>
<Accordion title="SSO buttons do not appear on the login page">
Verify the provider is **enabled** (toggle on the Active state) in **Settings → SSO** and that the configuration saved successfully. The login page fetches the list of enabled providers when it loads; hard-refresh the tab if changes were just made.
</Accordion>
</AccordionGroup>
The [operations troubleshooting page](/operations/troubleshooting#ldap-connection-refused) covers a few more cases that come up during initial setup, including LDAP connection refused, TLS certificate errors, and OAuth callback URL mismatches.
## Combining SSO with two-factor authentication
SSO and [two-factor authentication](/features/two-factor-authentication) work together. By default, SSO sign-ins skip the TOTP challenge, since the identity provider has already authenticated the user. Operators who want a stricter posture can flip **Require 2FA on SSO sign-in** on their **Settings → Account & Security** card to require both factors on every SSO sign-in. The toggle only appears once at least one SSO provider is enabled.