feat: SSO & LDAP authentication for Team Pro (#209)

* feat: SSO & LDAP authentication for Team Pro

Add SSO integration allowing Team Pro users to authenticate via LDAP/Active Directory, Google, GitHub, and Okta identity providers. SSO works alongside password authentication with auto-provisioning and role mapping.

- LDAP bind+search authentication with group-based role mapping
- OIDC/OAuth2 flows with PKCE and CSRF protection for Google, GitHub, Okta
- Auto-provisioning: first SSO login creates a Sencho account automatically
- Role mapping via LDAP group membership or OIDC JWT claims
- SSO settings UI in Settings → SSO with per-provider config and test connection
- SSO login buttons on login page with LDAP toggle
- Environment variable seeding for infrastructure-as-code workflows
- Secrets encrypted at rest via CryptoService (AES-256-GCM)
- Seat limit enforcement during auto-provisioning
- Full documentation: feature docs, quickstart guides, env var reference

* fix: resolve ESLint errors in SSO feature

- Remove unnecessary escape characters in regex character classes
- Remove unused `issuer` variable from OIDC callback handler
- Fix setState-in-effect lint error in Login.tsx by using useState initializer
- Suppress set-state-in-effect for SSOSection fetch pattern (matches existing codebase convention)
This commit is contained in:
Anso
2026-03-28 03:30:01 -04:00
committed by GitHub
parent b429097fa2
commit bd4008f509
20 changed files with 2247 additions and 14 deletions
+3 -1
View File
@@ -74,7 +74,8 @@
"pages": [
"getting-started/introduction",
"getting-started/quickstart",
"getting-started/configuration"
"getting-started/configuration",
"getting-started/sso-quickstart"
]
},
{
@@ -96,6 +97,7 @@
"features/atomic-deployments",
"features/fleet-backups",
"features/audit-log",
"features/sso",
"features/licensing"
]
},
+8
View File
@@ -49,6 +49,14 @@ Admins can manage accounts in **Settings → Users**. From there you can:
- **Edit** an existing user's password or role
- **Delete** a user account
## SSO auto-provisioning
With a Team Pro license, users can also be created automatically when they log in via SSO (LDAP, Google, GitHub, or Okta). SSO users appear in the Users list alongside local accounts. They are assigned a role based on identity provider group membership or claim mapping.
SSO users cannot log in with a password — they must always authenticate through their identity provider.
To set up identity provider authentication, see [SSO Authentication →](/features/sso).
## Migration from single-admin setup
When you upgrade to Sencho Pro, your existing single-admin credentials are automatically migrated to the new users table. No manual action is required - your login continues to work as before, and your account is assigned the Admin role.
+193
View File
@@ -0,0 +1,193 @@
---
title: SSO & LDAP Authentication
description: Authenticate with your existing identity provider — LDAP, Google, GitHub, or Okta.
---
<Note>
SSO requires a Sencho **Team Pro** license. Personal Pro and Community Edition do not include this feature.
</Note>
Sencho Team Pro lets your team sign in using existing identity providers instead of managing separate credentials. SSO works **alongside** password authentication — it does not replace it.
## Supported providers
| Provider | Protocol | Notes |
|----------|----------|-------|
| **LDAP / Active Directory** | LDAP bind + search | Works with OpenLDAP, Active Directory, FreeIPA, and any LDAPv3 server |
| **Google** | OpenID Connect | Google Workspace or personal Google accounts |
| **GitHub** | OAuth 2.0 | GitHub personal accounts and GitHub orgs |
| **Okta** | OpenID Connect | Any Okta org or Okta-compatible IdP |
## 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, searches for the user, then verifies their password
3. If this is their first login, a Sencho account is automatically created
4. Sencho issues a JWT and the user is logged in — identical to a password login
### OIDC / OAuth flow (Google, GitHub, Okta)
1. User clicks the provider button on the login page (e.g., "Sign in with Google")
2. Browser redirects 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. If this is their first login, a Sencho account is automatically created
6. Sencho issues a JWT and redirects to the dashboard
All OIDC flows use **PKCE** (Proof Key for Code Exchange) and a **state parameter** for CSRF protection.
## Auto-provisioning
When a user logs in via SSO for the first time, Sencho automatically creates a local account:
- **Username** is derived from their identity provider profile (display name, email prefix, or login handle)
- **Role** is assigned based on [role mapping](#role-mapping) — defaults to Viewer if no mapping matches
- **Password** is set to an unusable placeholder — SSO users cannot log in with a password
- **Seat limits** from your license apply. If admin seats are full, the user is downgraded to Viewer. If all seats are full, login is denied with a clear error message.
On subsequent logins, the existing account is reused. The user's email is updated if it changed at the provider.
## Role mapping
### LDAP group mapping
Set the **Admin Group DN** to a group in your directory. Members of that group get the Admin role; everyone else gets 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 who are a `member` of that group will be provisioned as Admin.
### OIDC claim mapping
For OIDC providers, configure two fields:
| Field | Description | Example |
|-------|-------------|---------|
| **Admin Claim** | The JWT 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 get Admin. Otherwise, they get the default role.
<Note>
Not all providers include a `groups` claim by default. You may need to configure custom claims in your identity provider's admin console.
</Note>
## Configuration
SSO can be configured two ways:
1. **Settings UI** — Go to **Settings → SSO** in the Sencho dashboard. Enable providers, enter credentials, and test connections from the UI. Changes take effect immediately without restarting.
2. **Environment variables** — Set `SSO_*` variables in your Docker Compose file. These seed the database on first boot. After that, the database configuration is authoritative.
### Via Settings UI
Admins can manage SSO providers in **Settings → SSO**. Each provider has:
- An **enable/disable** toggle
- Provider-specific configuration fields
- A **Test Connection** button to verify connectivity before saving
<Frame>
<img src="/images/sso/sso-settings.png" alt="SSO settings panel showing all four identity providers" />
</Frame>
Expand a provider card to configure it. Here's the LDAP configuration form:
<Frame>
<img src="/images/sso/sso-settings-ldap.png" alt="LDAP configuration form with server URL, bind DN, search base, and role mapping" />
</Frame>
And an OIDC provider (Google) configuration form:
<Frame>
<img src="/images/sso/sso-settings-oidc.png" alt="Google OIDC configuration form with client ID, client secret, and role claim mapping" />
</Frame>
### Via environment variables
Environment variables are useful for initial deployment or 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_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` | Whether to verify the LDAP server's TLS certificate |
### 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) |
### General
| Variable | Default | Description |
|----------|---------|-------------|
| `SSO_OIDC_ADMIN_CLAIM` | `groups` | JWT 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 access Sencho from — 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.
## Security
- **PKCE** — All OIDC flows use `code_challenge_method=S256` to prevent authorization code interception
- **State parameter** — A cryptographic random value protects against CSRF attacks on the OAuth callback
- **Encrypted secrets** — LDAP bind passwords and OIDC client secrets are encrypted at rest with AES-256-GCM
- **No local password** — SSO users are created with an unusable password hash. They cannot bypass SSO by using the password login form
- **Admin-only configuration** — Only Team Pro administrators can enable or configure SSO providers
## Troubleshooting
### LDAP connection refused
Verify the LDAP server is reachable from the Sencho container. If LDAP is on the host machine, use `host.docker.internal` (Docker Desktop) or the host's LAN IP address — not `localhost`.
### TLS certificate errors
If your LDAP server uses a self-signed certificate, set `SSO_LDAP_TLS_REJECT_UNAUTHORIZED=false`. For production, install a trusted certificate instead.
### OAuth callback URL mismatch
The redirect URI registered in your identity provider must exactly match what Sencho sends. Check:
1. `SSO_CALLBACK_URL` is set to your external URL (e.g., `https://sencho.example.com`)
2. The callback URL in your provider's settings is `https://sencho.example.com/api/auth/sso/oidc/<provider>/callback`
3. Protocol matches — don't mix `http` and `https`
### SSO buttons not appearing on login page
SSO providers only appear on the login page when they are both **configured** and **enabled**. Check Settings → SSO to verify the provider is active.
+14
View File
@@ -27,6 +27,20 @@ When you point `COMPOSE_DIR` at a directory, Sencho expects each stack to live i
| `DATA_DIR` | `/app/data` | Directory where Sencho stores its SQLite database, node registry, and cached metrics. |
| `NODE_ENV` | `production` | Set automatically in the Docker image. Only change this for local development. |
## SSO environment variables
If you use SSO (Team Pro), configure your identity providers via environment variables:
| Variable | Description |
|----------|-------------|
| `SSO_LDAP_ENABLED` | Enable LDAP/AD authentication |
| `SSO_OIDC_GOOGLE_ENABLED` | Enable Google SSO |
| `SSO_OIDC_GITHUB_ENABLED` | Enable GitHub SSO |
| `SSO_OIDC_OKTA_ENABLED` | Enable Okta SSO |
| `SSO_CALLBACK_URL` | External base URL for OAuth callbacks (required behind reverse proxy) |
For the full SSO configuration reference and setup guides, see [SSO Authentication →](/features/sso).
## Required volume mounts
### Docker socket
+153
View File
@@ -0,0 +1,153 @@
---
title: SSO Setup Guide
description: Step-by-step instructions for connecting Sencho to your identity provider.
---
<Note>
SSO requires a Sencho **Team Pro** license. You can configure SSO via environment variables (shown below) or from the Settings UI after first boot.
</Note>
## Google OIDC
1. Go to the [Google Cloud Console](https://console.cloud.google.com/apis/credentials)
2. Create a new **OAuth 2.0 Client ID** (Application type: Web application)
3. Add an **Authorized redirect URI**: `https://sencho.example.com/api/auth/sso/oidc/oidc_google/callback`
4. Copy the **Client ID** and **Client Secret**
5. Add to your `docker-compose.yml`:
```yaml
services:
sencho:
image: saelix/sencho:latest
environment:
- COMPOSE_DIR=/opt/compose
- SSO_OIDC_GOOGLE_ENABLED=true
- SSO_OIDC_GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
- SSO_OIDC_GOOGLE_CLIENT_SECRET=your-client-secret
- SSO_CALLBACK_URL=https://sencho.example.com
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./sencho-data:/app/data
- /opt/compose:/opt/compose
```
Restart Sencho. A "Google" button will appear on the login page.
## GitHub OAuth
1. Go to **GitHub → Settings → Developer Settings → [OAuth Apps](https://github.com/settings/developers)**
2. Click **New OAuth App**
3. Set:
- **Application name**: Sencho
- **Homepage URL**: `https://sencho.example.com`
- **Authorization callback URL**: `https://sencho.example.com/api/auth/sso/oidc/oidc_github/callback`
4. Copy the **Client ID** and generate a **Client Secret**
5. Add to your `docker-compose.yml`:
```yaml
environment:
- SSO_OIDC_GITHUB_ENABLED=true
- SSO_OIDC_GITHUB_CLIENT_ID=your-github-client-id
- SSO_OIDC_GITHUB_CLIENT_SECRET=your-github-client-secret
- SSO_CALLBACK_URL=https://sencho.example.com
```
## Okta OIDC
1. In the [Okta Admin Console](https://admin.okta.com), go to **Applications → Create App Integration**
2. Select **OIDC - OpenID Connect** and **Web Application**
3. Set the **Sign-in redirect URI** to: `https://sencho.example.com/api/auth/sso/oidc/oidc_okta/callback`
4. Note your **Okta domain** (e.g., `https://dev-123456.okta.com`)
5. Copy the **Client ID** and **Client Secret**
6. Add to your `docker-compose.yml`:
```yaml
environment:
- SSO_OIDC_OKTA_ENABLED=true
- SSO_OIDC_OKTA_ISSUER_URL=https://dev-123456.okta.com
- SSO_OIDC_OKTA_CLIENT_ID=your-okta-client-id
- SSO_OIDC_OKTA_CLIENT_SECRET=your-okta-client-secret
- SSO_CALLBACK_URL=https://sencho.example.com
```
## LDAP / Active Directory
1. Identify your LDAP server's URL and port (default: `389` for LDAP, `636` for LDAPS)
2. Create a **read-only service account** (bind DN) that can search the user directory
3. Determine the **search base** (where users live in the directory tree)
4. Choose the right **search filter**:
- OpenLDAP: `(uid={{username}})`
- Active Directory: `(sAMAccountName={{username}})`
5. Optionally identify an **admin group DN** for role mapping
6. Add to your `docker-compose.yml`:
```yaml
environment:
- SSO_LDAP_ENABLED=true
- SSO_LDAP_URL=ldap://ldap.example.com:389
- SSO_LDAP_BIND_DN=cn=readonly,dc=example,dc=com
- SSO_LDAP_BIND_PASSWORD=your-bind-password
- SSO_LDAP_SEARCH_BASE=ou=users,dc=example,dc=com
- SSO_LDAP_SEARCH_FILTER=(uid={{username}})
- SSO_LDAP_ADMIN_GROUP_DN=cn=sencho-admins,ou=groups,dc=example,dc=com
- SSO_LDAP_DEFAULT_ROLE=viewer
```
After starting Sencho, verify the connection in **Settings → SSO → Test Connection**.
<Warning>
If your LDAP server is on the Docker host (not in a container), use the host's LAN IP or `host.docker.internal` (Docker Desktop) instead of `localhost`.
</Warning>
## Role mapping
By default, all SSO users are assigned the **Viewer** role. To grant Admin to specific users:
**For LDAP**: Set `SSO_LDAP_ADMIN_GROUP_DN` to the DN of your admin group. Users who are members of that group will be provisioned as Admin.
**For OIDC**: Set these two variables:
```yaml
environment:
- SSO_OIDC_ADMIN_CLAIM=groups
- SSO_OIDC_ADMIN_CLAIM_VALUE=sencho-admins
```
This tells Sencho to check the `groups` claim in the OIDC ID token. If it contains `sencho-admins`, the user gets Admin.
## Full docker-compose.yml example with SSO
```yaml
services:
sencho:
image: saelix/sencho:latest
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./sencho-data:/app/data
- /opt/compose:/opt/compose
environment:
- COMPOSE_DIR=/opt/compose
- DATA_DIR=/app/data
# Google SSO
- SSO_OIDC_GOOGLE_ENABLED=true
- SSO_OIDC_GOOGLE_CLIENT_ID=your-google-client-id
- SSO_OIDC_GOOGLE_CLIENT_SECRET=your-google-secret
# LDAP
- SSO_LDAP_ENABLED=true
- SSO_LDAP_URL=ldap://ldap.example.com:389
- SSO_LDAP_BIND_DN=cn=readonly,dc=example,dc=com
- SSO_LDAP_BIND_PASSWORD=your-bind-password
- SSO_LDAP_SEARCH_BASE=ou=users,dc=example,dc=com
- SSO_LDAP_SEARCH_FILTER=(sAMAccountName={{username}})
- SSO_LDAP_ADMIN_GROUP_DN=cn=sencho-admins,ou=groups,dc=example,dc=com
# Role mapping & callback
- SSO_OIDC_ADMIN_CLAIM=groups
- SSO_OIDC_ADMIN_CLAIM_VALUE=sencho-admins
- SSO_DEFAULT_ROLE=viewer
- SSO_CALLBACK_URL=https://sencho.example.com
```
For the complete list of environment variables and their defaults, see [SSO & LDAP Authentication →](/features/sso#sso-environment-variables-reference).
Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB