Dockerize SSO Manager (all-in-one image) + GitHub Pages docs

All-in-one Dockerfile.openldap bundling the app + OpenLDAP + Redis in one
container, plus an idempotent bare-metal install.sh, and a Jekyll docs site
for GitHub Pages:
- Dockerfile.openldap (node:20-alpine; openldap + pw-sha2/ppolicy/memberof/
  refint; dumb-init PID 1; npm ci --omit=dev; tos.md copied to /).
- docker-entrypoint.sh: generate slapd.conf (mdb + overlays + TLS + indexes +
  access), self-signed LDAPS cert, seed directory tree + required groups,
  bundled redis, export app_* config, exec node.
- docker-compose.yml, .dockerignore, DEPLOYMENT.md, secrets.js.example.
- install.sh: idempotent Debian/Ubuntu bare-metal installer (Node 20.x,
  OpenLDAP, Redis, systemd unit) with flags + --dry-run/--skip-ldap/--skip-app.
- ops/ldif/: memberof/refint/tls/index/nodes/logging LDIFs.
- nodejs/conf/base.js: generic defaults (dc=example,dc=com / localhost /
  SSO Manager) so per-deployment values move to secrets.js or app_* env.
- nodejs/package.json: bump @simpleworkjs/conf to ^1.1.0 (app_* env overrides).
- nodejs/routes/index.js: /health endpoint for healthchecks.
- docs/: _config.yml + index/deployment/configuration/oauth/ldap pages
  (jekyll-theme-cayman) for GitHub Pages from /docs.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-11 17:03:16 -04:00
parent e28fb63784
commit fe9b7c168b
27 changed files with 2397 additions and 16 deletions
+9
View File
@@ -0,0 +1,9 @@
title: SSO Manager
description: A self-hosted OpenID Connect provider with an OpenLDAP directory and a web management UI
theme: jekyll-theme-cayman
show_downloads: true
github:
repository_url: https://github.com/theta42/sso-manager-node
zip_url: https://github.com/theta42/sso-manager-node/archive/refs/heads/master.zip
tar_url: https://github.com/theta42/sso-manager-node/archive/refs/heads/master.tar.gz
repository_name: theta42/sso-manager-node
+99
View File
@@ -0,0 +1,99 @@
---
layout: default
title: Configuration
---
# Configuration
[← Back to Home](index.html)
The app loads configuration via
[`@simpleworkjs/conf`](https://www.npmjs.com/package/@simpleworkjs/conf), which
deep-merges, in order (later wins):
1. `conf/base.js` — committed, generic defaults (`dc=example,dc=com`,
`localhost`, `SSO Manager`).
2. `conf/<NODE_ENV>.js` — optional, environment-specific.
3. `conf/secrets.js` — gitignored; secrets + per-deployment values.
4. **`app_*` environment variables** — the highest-precedence layer.
Any env var whose name starts with `app_` overrides the merged config. The rest
of the name splits on **double-underscore** (`__`) into a nested path. Values are
`JSON.parse`-coerced when possible (numbers, booleans, null, JSON) and kept as
raw strings otherwise.
## Examples
| Env var | Sets | Type |
|---------|------|------|
| `app_ldap__url=ldap://host:389` | `conf.ldap.url` | string |
| `app_ldap__bindPassword=secret` | `conf.ldap.bindPassword` | string |
| `app_ldap__userBase=ou=people,dc=…` | `conf.ldap.userBase` | string |
| `app_oauth__jwtSecret=...` | `conf.oauth.jwtSecret` | string |
| `app_oauth__issuer=https://sso.example.com` | `conf.oauth.issuer` | string |
| `app_oauth__token_lifetime__access_token=3600` | `conf.oauth.token_lifetime.access_token` | number |
| `app_smtp__secure=false` | `conf.smtp.secure` | boolean |
| `app_smtp__host=smtp.example.com` | `conf.smtp.host` | string |
| `app_name=My SSO` | `conf.name` | string |
| `app_redis__host=redis.local` | `conf.redis.host` | string (external Redis) |
## The `app_*` env layer requires conf >= 1.1.0
The `app_*` environment-variable override layer was added in
`@simpleworkjs/conf` **1.1.0**. On 1.0.0 the app ignores all `app_*` vars and only
reads `base.js` / `<NODE_ENV>.js` / `secrets.js`. The Docker image will not honor
`app_*` env on 1.0.0. Refresh the lock from the `nodejs/` directory:
```bash
cd nodejs && npm install @simpleworkjs/conf@^1.1.0
```
## Inspecting the merged config
From the `nodejs/` directory:
```bash
node -e "console.log(require('@simpleworkjs/conf').ldap)"
node -e "console.log(require('@simpleworkjs/conf').oauth)"
node -e "console.log(require('@simpleworkjs/conf'))" # everything
```
Or, inside the running container:
```bash
docker compose exec sso-manager node -e "console.log(require('@simpleworkjs/conf').ldap)"
```
`app_*` env vars override `secrets.js`, which overrides `base.js` — if a value
isn't what you expect, check those layers in that order.
## Migrating an existing instance to the generic defaults
The committed `nodejs/conf/base.js` ships **generic** defaults
(`dc=example,dc=com`, `localhost`, `SSO Manager`). Previously it carried
Theta42-specific values (LDAP bind DN/bases, SMTP host/user/sender, OAuth
issuer). If you run an existing instance off this repo:
- Move per-deployment, non-secret values (bind DN, user/group bases, SMTP
host/user/sender, OAuth issuer, org name) from `base.js` into your gitignored
`conf/secrets.js`, **or** set them as `app_*` env vars.
- Secret values (LDAP bind password, SMTP password, JWT secret) already belong
in `secrets.js`.
## Troubleshooting `app_*` env vars
### `app_*` vars seem to do nothing
You're on `@simpleworkjs/conf` 1.0.0. Bump to 1.1.0+ (above).
### LDAP operations 401 / "Invalid Credentials"
Check the merged LDAP config the app actually sees:
```bash
cd nodejs && node -e "console.log(require('@simpleworkjs/conf').ldap)"
```
Confirm `url` / `bindDN` / `bindPassword` / `userBase` match your directory.
[← Back to Home](index.html)
+177
View File
@@ -0,0 +1,177 @@
---
layout: default
title: Deployment
---
# Deployment Guide
[← Back to Home](index.html)
Two supported methods:
1. **Docker** — a single all-in-one image bundling the app + OpenLDAP + Redis.
2. **Bare metal**`install.sh` on Debian/Ubuntu (Node.js, OpenLDAP, Redis, systemd unit).
## Method 1: Docker (all-in-one)
The image (`Dockerfile.openldap`) bundles OpenLDAP + the app + Redis in one
container. The app connects to the bundled slapd over `localhost:389`
automatically; you only need to set a few secrets.
```bash
# Minimal: an LDAP admin password + a JWT secret, then build + start.
LDAP_ADMIN_PASS='choose-a-strong-password' \
JWT_SECRET="$(openssl rand -hex 32)" \
docker compose up -d --build
```
For a customized deployment, put overrides in a `.env` next to
`docker-compose.yml`:
```env
LDAP_BASE_DN=dc=yourdomain,dc=com
LDAP_DOMAIN=yourdomain.com
LDAP_ADMIN_PASS=your-admin-password
ORG_NAME=Your Org
JWT_SECRET=your-jwt-secret
OAUTH_ISSUER=https://sso.yourdomain.com # browser-facing URL the proxy serves
LDAP_CERT_CN=sso.yourdomain.com # hostname LDAPS clients verify
SMTP_HOST=smtp.yourdomain.com
SMTP_PORT=587
SMTP_USER=noreply@yourdomain.com
SMTP_PASS=your-smtp-password
SMTP_FROM=Your Org <noreply@yourdomain.com>
PORT=3001
LDAPS_PORT=636
```
Then `docker compose up -d --build`.
### Access
- SSO Manager UI: `http://localhost:3001` (HTTP — put a TLS-terminating proxy in front)
- Health: `http://localhost:3001/health``{"status":"ok"}`
- OIDC discovery: `http://localhost:3001/.well-known/openid-configuration`
- LDAPS (legacy apps / direct binds): `ldaps://<host>:636`
### Environment variables
| Variable | Default | Description |
|----------|---------|-------------|
| `LDAP_BASE_DN` | `dc=example,dc=com` | slapd suffix + app user/group base |
| `LDAP_DOMAIN` | derived from base DN | DNS domain; default for cert CN + issuer |
| `LDAP_ADMIN_PASS` | `admin` | slapd root password + app bind password |
| `ORG_NAME` | `SSO Manager` | org name in UI/email/group descriptions |
| `JWT_SECRET` | auto-generated | OAuth JWT signing secret (**persist it**) |
| `OAUTH_ISSUER` | `https://sso.<LDAP_DOMAIN>` | OIDC issuer (browser-facing URL) |
| `LDAP_CERT_CN` | `LDAP_DOMAIN` | CN/SAN on the LDAPS cert |
| `LDAP_CERT_DIR` | `/etc/openldap/certs` | look for `ldap.crt`+`ldap.key` here (mount your own) |
| `SMTP_HOST`/`SMTP_PORT`/`SMTP_USER`/`SMTP_PASS`/`SMTP_FROM` | localhost / 587 / empty | outbound email |
| `PORT` | `3001` | host port mapped to the UI |
| `LDAPS_PORT` | `636` | host port mapped to LDAPS |
Any `app_*` var may also be set directly to override any config value — see
[Configuration](configuration.html).
### LDAP TLS (LDAPS / StartTLS)
The bundled slapd generates a **self-signed cert** on first start (CN =
`LDAP_CERT_CN`, valid 10y, SAN = CN + `localhost` + `127.0.0.1`) and listens on
`ldaps:///` (636) + StartTLS on `ldap:///` (389). The cert lives on the
`ldap-certs` volume so it persists across container recreation.
- **Trust it** (clients): copy the cert out and add it to the client's CA store,
or set `TLS_REQCERT never` for quick LAN use:
```bash
docker compose cp sso-manager:/etc/openldap/certs/ldap.crt ./ldap.crt
```
- **Use your own cert**: replace the `ldap-certs` named volume with a bind mount
containing your `ldap.crt` + `ldap.key`. The entrypoint leaves existing certs
untouched.
> Port 389 (plain LDAP) is **not** mapped to the host by default — direct-LDAP
> clients should use LDAPS (636) or StartTLS.
### Backups (~100 users)
```bash
docker compose exec sso-manager slapcat -f /etc/openldap/slapd.conf \
-b "dc=yourdomain,dc=com" > ldap-backup-$(date +%F).ldif
```
Restorable with `ldapadd`/`ldapmodify` against a fresh instance. Redis is
in-memory (session/cache only — safe to lose). Persist `JWT_SECRET` +
`LDAP_ADMIN_PASS` outside the container (your `.env`, a password manager).
## Method 2: Bare metal (Debian/Ubuntu)
`install.sh` is an idempotent installer: Node.js 20.x, OpenLDAP (modules +
overlays + custom schema + directory tree + required groups), the app at
`/opt/sso-manager`, and a systemd unit.
```bash
sudo ./install.sh \
-p 'your-ldap-password' \
-b 'dc=yourdomain,dc=com' \
-n 'Your Org' \
-o 3001
```
| Flag | Description |
|------|-------------|
| `-p, --admin-pass` | LDAP admin password (required) |
| `-b, --base-dn` | Base DN (default `dc=example,dc=com`) |
| `-n, --org-name` | Org name (default `SSO Manager`) |
| `-o, --port` | HTTP port (default `3001`) |
| `-j, --jwt-secret` | JWT secret (default auto-generated) |
| `-s, --smtp-config` | SMTP as `host:port:user:pass` |
| `--skip-ldap` | Skip LDAP setup (use existing) |
| `--skip-app` | LDAP setup only |
| `--dry-run` | Show actions without making changes |
Post-install:
```bash
sudo systemctl enable --now sso-manager
curl http://localhost:3001/health # -> {"status":"ok"}
```
For an existing LDAP server, run `sudo ./install.sh --skip-ldap …` and point the
app at it. To (re)configure overlays on an already-installed slapd, prefer
`ops/ldap-setup.sh` (idempotent, auto-detects the user database).
## Fronting with a reverse proxy
The SSO runs HTTP inside the container; terminate TLS at a front proxy. The
[`theta42/proxy`](https://github.com/theta42/proxy) is an OIDC-protected reverse
proxy and a natural fit — it's **both** an OIDC client of this SSO *and* a
direct LDAP client for user lookups.
1. **One Docker network** so the proxy reaches the SSO internally at
`http://sso-manager:3001` (token/userinfo, server-to-server) without exposing
the SSO's HTTP port.
2. **Set the SSO's `OAUTH_ISSUER`** to the *browser-facing* HTTPS URL the proxy
serves the SSO at (e.g. `https://sso.yourdomain.com`).
3. **Register the proxy as an OIDC client** in the SSO UI, with `redirectUri`
matching the proxy's callback (`https://proxy.yourdomain.com/api/auth/oidc/callback`).
4. **LDAP for the proxy**: point `ldap.url` at `ldaps://sso-manager:636` and
create a dedicated service account under `ou=people` (e.g.
`cn=ldapclient,ou=people,…`) — don't reuse the admin DN.
The [`theta42/theta-env`](https://github.com/theta42/theta-env) unified repo
automates all four steps with `./setup.sh` — see
[theta-env docs](https://theta42.github.io/theta-env/).
## Security notes
1. **Never commit `secrets.js`** — it's in `.gitignore`.
2. **Use LDAPS / StartTLS** for any LDAP that crosses the network. Port 389 is
not mapped to the host by default so LAN clients can't bind in cleartext.
3. **Persist `JWT_SECRET`** — an auto-generated one invalidates all tokens on
container recreation.
4. **Don't expose the UI's HTTP port to the internet** — terminate TLS at a
front proxy and keep `3001` on the Docker network / localhost only.
5. The all-in-one image runs slapd as the `ldap` user but the app as root
(matches the bare-metal unit). Harden to a non-root user for production.
[← Back to Home](index.html)
+105
View File
@@ -0,0 +1,105 @@
---
layout: default
title: Home
---
# SSO Manager
A self-hosted **OpenID Connect provider** with a bundled **OpenLDAP directory**
and a web management UI — for home labs and small businesses that want their
own identity provider instead of a hosted one.
## Features
- **OpenID Connect / OAuth 2.0 provider** — issue your own access/refresh/id
tokens; protect your apps with OIDC login.
- **OpenLDAP directory** — users, groups, POSIX accounts (`posixAccount`/
`inetOrgPerson`), SSH public keys, and sudo roles, with `memberOf` +
referential-integrity overlays.
- **Web management UI** — manage users, groups, and OAuth clients from a
browser; invite/password-reset flows over email.
- **LDAPS for legacy apps** — apps that bind LDAP directly (Gitea, Emby, …)
can use LDAPS (636) / StartTLS.
- **All-in-one Docker image** — app + OpenLDAP + Redis in one container, or
run each piece separately via `app_*` env config.
## Quick Start
### Docker (all-in-one)
```bash
git clone https://github.com/theta42/sso-manager-node.git
cd sso-manager-node
cp secrets.js.example nodejs/conf/secrets.js # edit it, or use app_* env
docker compose up -d --build
```
The web UI comes up at `http://localhost:3001`. See the
[Deployment Guide](deployment.html) for the full set of `app_*` env vars.
### Bare metal (Debian/Ubuntu)
```bash
sudo ./install.sh
```
Idempotent installer — installs Node.js, OpenLDAP, Redis, configures the app,
and starts a systemd unit. Re-run to update.
### Run it together with the proxy
The proxy ([theta42/proxy](https://github.com/theta42/proxy)) fronts this SSO
under TLS and protects it with OIDC, while also binding LDAP directly. Run both
with one command via [theta-env](https://github.com/theta42/theta-env):
```bash
git clone --recursive https://github.com/theta42/theta-env.git
cd theta-env && cp .env.example .env # edit, then:
./setup.sh
```
## Documentation
- [Deployment Guide](deployment.html) — Docker + bare metal, the config layers,
the `app_*` env reference, backups.
- [Configuration](configuration.html) — every `app_*` env var and the conf
merge order.
- [OAuth / OIDC](oauth.html) — the provider: discovery, client management,
token lifetimes, scopes.
- [LDAP](ldap.html) — directory layout, TLS, overlays, schema, direct-bind
service accounts.
## Architecture
```
┌─────────────┐
│ Browser / │
│ OIDC apps │
└──────┬──────┘
│ HTTP/HTTPS
┌────────────────────────┐ ┌─────────────┐
│ Express SSO Manager │◄────►│ Redis │
│ - OIDC provider │ │ - sessions │
│ - web UI (:3001) │ │ - models │
│ - management API │ └─────────────┘
└────────┬───────────────┘
│ ldapi/ldap (localhost)
┌────────────────────────┐
│ OpenLDAP (slapd) │
│ - users / groups │
│ - LDAPS :636 │─── legacy apps bind directly
│ - StartTLS :389 │
└────────────────────────┘
```
## Community
- [GitHub Repository](https://github.com/theta42/sso-manager-node)
- [Issue Tracker](https://github.com/theta42/sso-manager-node/issues)
- [Pull Requests](https://github.com/theta42/sso-manager-node/pulls)
## License
MIT License — see the repository for details.
+153
View File
@@ -0,0 +1,153 @@
---
layout: default
title: LDAP
---
# LDAP Directory
[← Back to Home](index.html)
SSO Manager runs an OpenLDAP directory holding your users and groups. The app
authenticates against it over `localhost:389` (inside the all-in-one container)
and exposes **LDAPS** (`ldaps://…:636`, TLS) for legacy apps that bind LDAP
directly — Gitea, Emby, the theta42/proxy, etc.
## Directory layout
```
dc=yourdomain,dc=com
├── ou=people users (inetOrgPerson + posixAccount + …)
├── ou=groups groups (groupOfNames)
└── ou=policies password policies (pwdPolicy)
└── cn=ppolicy default policy
```
### Users
User entries are `cn=<uid>,ou=people,<base>` and carry the objectClasses:
- `inetOrgPerson` (cn, sn, mail, …) — identity / contact attrs.
- `posixAccount` (uid, uidNumber, gidNumber, homeDirectory) — the SSO's
`userFilter` is `(objectClass=posixAccount)`, so a user is "a real account"
iff it has `posixAccount`.
- `ldapPublicKey` — SSH public keys (`sshPublicKey`).
- `sudoRole` — per-user sudo rules (`sudoCommand`, `sudoHost`, `sudoUser`).
- `theta42Person` (custom auxiliary; `dateOfBirth`).
Passwords are stored as `{SSHA512}` (8-byte salt, sha512(pass+salt), base64),
verified by the `pw-sha2` module. The app's `hashPasswordSSHA512` is the
canonical hasher; if you provision users out-of-band, hash passwords the same
way or use `slappasswd -h '{SSHA512}'`.
### Groups
Groups are `cn=<name>,ou=groups,<base>` (`groupOfNames`) with a `member`
attribute listing member DNs. The `memberOf` overlay populates reverse
membership (`memberOf` on the user); `refint` keeps it consistent on
add/remove. **Admin permission checks read the group's `member` list**, not
`memberOf` on the user.
The SSO requires three groups (seeded automatically by the entrypoint /
`install.sh`):
| Group | Grants |
|-------|--------|
| `app_sso_admin` | full admin (users, groups, settings) |
| `app_sso_oauth_admin` | OAuth client management |
| `app_sso_invite` | invitation management |
## TLS (LDAPS / StartTLS)
The bundled slapd generates a **self-signed cert** on first start (CN =
`LDAP_CERT_CN`, valid 10y, SAN = CN + `localhost` + `127.0.0.1`) and listens on:
- `ldaps:///`**636**, TLS (the port to expose for direct-LDAP clients).
- `ldap:///`**389**, plain + StartTLS (not mapped to the host by default).
The cert lives on the `ldap-certs` volume so it persists across container
recreation.
### Trusting the self-signed cert
Copy it out and add it to the client's CA store:
```bash
docker compose cp sso-manager:/etc/openldap/certs/ldap.crt ./ldap.crt
```
…or, for quick LAN use, set `TLS_REQCERT never` on the client (the theta42/proxy
sets `app_ldap__tlsOptions__rejectUnauthorized=false` for the same effect).
### Using your own cert
Replace the `ldap-certs` named volume with a bind mount containing your own
`ldap.crt` + `ldap.key`:
```yaml
volumes:
- ./certs:/etc/openldap/certs # must contain ldap.crt + ldap.key
```
The entrypoint leaves existing certs untouched (idempotent).
## Direct-bind service accounts
For apps that bind LDAP directly, create a dedicated **service account** under
`ou=people` (e.g. `cn=ldapclient,ou=people,<base>`) with a strong password —
**don't reuse the admin DN**. The theta-env bootstrap creates this account
automatically (`cn=ldapclient`) and the proxy binds as it.
Example bind test:
```bash
ldapsearch -x -H ldaps://sso.example.com:636 \
-D "cn=ldapclient,ou=people,dc=yourdomain,dc=com" -W \
-b "ou=people,dc=yourdomain,dc=com" '(objectClass=posixAccount)' cn mail
```
## Modules + overlays (external LDAP servers)
If you point the app at your own LDAP server instead of the bundled slapd, it
needs:
- **Modules:** `pw-sha2` (the app stores user passwords as `{SSHA512}`),
`ppolicy`, `memberof`, `refint`.
- **Custom schema:** the `theta42Person` auxiliary objectClass with
`dateOfBirth` — see `ops/ldap-setup.sh` for the LDIF.
- **Directory tree:** `ou=people`, `ou=groups`, `ou=policies` under the base DN,
a default `pwdPolicy` at `cn=ppolicy,ou=policies,<base>`.
- **Required groups:** `app_sso_admin`, `app_sso_invite`, `app_sso_oauth_admin`.
`ops/ldap-setup.sh -p <admin-password>` configures all of the above
idempotently against a running slapd (auto-detects the database holding your
base DN, and verifies `pwdAccountLockedTime` is live — the attribute the app's
active/inactive toggle depends on).
## Backups
```bash
docker compose exec sso-manager slapcat -f /etc/openldap/slapd.conf \
-b "dc=yourdomain,dc=com" > ldap-backup-$(date +%F).ldif
```
Restorable with `ldapadd`/`ldapmodify` against a fresh instance.
## Troubleshooting
### `503 OpenLDAP ppolicy overlay is not configured`
The ppolicy overlay isn't attached to the database holding your users, so the
active/inactive toggle can't set `pwdAccountLockedTime`:
```bash
sudo ./ops/ldap-setup.sh -p 'admin-password' -b dc=yourdomain,dc=com
```
### LDAP connection refused
```bash
docker compose exec sso-manager sh -c 'ldapsearch -x -H ldap://localhost:389 -b "" -s base'
systemctl status slapd # bare metal
```
[← Back to Home](index.html)
+105
View File
@@ -0,0 +1,105 @@
---
layout: default
title: OAuth / OIDC
---
# OAuth 2.0 / OpenID Connect
[← Back to Home](index.html)
SSO Manager is an **OpenID Connect / OAuth 2.0 provider**: it issues its own
access, refresh, and ID tokens that your apps can consume to authenticate
users and authorize API calls. It also runs a full OpenLDAP directory, so it
can be both your SSO and your user directory at once.
## Discovery
The provider publishes a standards-compliant discovery document:
```
GET https://<sso-host>/.well-known/openid-configuration
```
It advertises the `issuer`, `authorization_endpoint`, `token_endpoint`,
`userinfo_endpoint`, `end_session_endpoint`, supported scopes, and token
lifetimes. OIDC clients (e.g. the theta42/proxy) can read their endpoint URLs
from here rather than configuring each one.
The `issuer` advertised is `conf.oauth.issuer` — set it to the **browser-facing**
HTTPS URL the SSO is served at (e.g. `https://sso.example.com`), either in
`conf/secrets.js` or via `app_oauth__issuer` / `OAUTH_ISSUER`.
## OAuth clients
An OAuth client represents an app that authenticates against the SSO. Each has:
- `client_id` (UUID) + `client_secret` (bcrypt-hashed; the **raw secret is
shown once** when the client is created or rotated — save it immediately).
- `name`, `description`, `created_by` (the admin uid that created it).
- `redirect_uris` — allowed callback URLs (must match exactly).
- `scopes` — requested scopes (default `openid profile email groups`).
- `allowed_groups` — restrict the client to members of specific SSO groups
(empty = any valid user).
- `token_lifetime``access_token` / `refresh_token` lifetimes (seconds).
### Managing clients
Clients are managed from the web UI (as a member of the `app_sso_oauth_admin`
group) or the HTTP API at `/api/oauth/client` (auth via the `auth-token` header
from a login):
| Method | Path | Action |
|--------|------|--------|
| `GET` | `/api/oauth/client` | list clients |
| `POST` | `/api/oauth/client` | create a client (returns the raw `client_secret` once) |
| `GET` | `/api/oauth/client/:id` | get one |
| `PUT` | `/api/oauth/client/:id` | update redirect URIs / scopes / groups |
| `DELETE` | `/api/oauth/client/:id` | delete |
| `POST` | `/api/oauth/client/:id/rotate` | rotate the secret (returns the new raw secret once) |
> All client-management endpoints are gated by the `app_sso_oauth_admin` group.
## Scopes
| Scope | Claims / access |
|-------|-----------------|
| `openid` | OIDC ID token + discovery |
| `profile` | `preferred_username`, display name, etc. |
| `email` | the user's `mail` |
| `groups` | the user's group memberships (the `groups` claim) |
The `groups` claim is what relying parties (e.g. the proxy's
`app_auth__adminGroups`) use to map group membership to roles.
## Token lifetimes
Defaults (overridable per-client via `token_lifetime`, or globally via
`app_oauth__token_lifetime__access_token` /
`app_oauth__token_lifetime__refresh_token`):
- access token: 3600s (1 hour)
- refresh token: 2592000s (30 days)
## Admin gating
SSO admin actions are gated by LDAP group membership (checked via the group's
`member` list, not `memberOf` on the user):
- `app_sso_admin` — full admin (users, groups, settings).
- `app_sso_oauth_admin` — OAuth client management.
- `app_sso_invite` — invitation management.
The bootstrap in [theta-env](https://github.com/theta42/theta-env) creates your
first admin and adds them to `app_sso_admin` + `app_sso_oauth_admin`
automatically; for a standalone install, add the admin's DN to those groups
manually (or via `ops/ldap-setup.sh`).
## JWT signing
Tokens are signed with `conf.oauth.jwtSecret` (`app_oauth__jwtSecret` /
`JWT_SECRET`). **Persist this secret** — if it changes, every issued token
stops validating. The all-in-one Docker image auto-generates one if none is set,
but that generated value does not survive container recreation unless you
persist it (set `JWT_SECRET` in your `.env`).
[← Back to Home](index.html)