SSO setup reference
Use this reference while following one provider quickstart.
Sign-in flow at a glance
PKCE (Proof Key for Code Exchange) is an OIDC hardening step, enabled by default.
Configuration surfaces
| Surface | Use for |
|---|---|
| System Settings → Manage SSO providers | Create, edit, test, and enable providers; copy slug-scoped callback URLs |
| System Settings → Single Sign-On | IdP group to TokenTimer role bindings |
Helm ssoProviders[] | GitOps bootstrap; chart renders sso-providers.json ConfigMap |
Compose sso-providers.json | Local/bootstrap manifest (bind-mounted at /etc/tokentimer/sso-providers.json) |
SSO_<SLUG>_* env vars | Secret material and GitOps field locks per provider (see Configuration reference) |
Prerequisites (all providers)
| Requirement | Notes |
|---|---|
| Enterprise license | feature:oidc and/or feature:saml on the JWT |
| Public API URL | Must match what browsers and the IdP use for callbacks (API_URL) |
| HTTPS in production | Session cookies expect TLS (APP_URL, API_URL) |
TRUST_PROXY_HOPS | Match your ingress/LB chain (default 2). Wrong values break callback URLs behind TLS |
| Ingress | Route /auth/* to the API service, not only /api/* |
| Secrets keyring | An encryption keyring for SSO secrets. Helm auto-creates <release>-enterprise-secrets by default; Compose uses SSO_SECRETS_KEYRING_FILE. To supply your own, follow Keyring JSON format |
URLs TokenTimer uses
Replace https://tokentimer.example.com with your deployment (API_URL) and
<slug> with the provider slug you chose under Manage SSO providers or in the bootstrap
manifest.
| Purpose | OIDC | SAML |
|---|---|---|
| Browser sign-in start | GET /auth/oidc/<slug> | GET /auth/saml/<slug> |
| Callback (register in IdP) | GET /auth/oidc/<slug>/callback | POST /auth/saml/<slug>/callback (the ACS URL, see below) |
| Default callback if unset | {API_URL}/auth/oidc/<slug>/callback | {API_URL}/auth/saml/<slug>/callback |
| SP metadata (export to IdP) | n/a | GET {API_URL}/auth/saml/<slug>/metadata |
SAML terminology: the ACS (Assertion Consumer Service) URL is the address your IdP posts the SAML response to, and the SP (service provider) is TokenTimer's role in the SAML exchange.
API_URL / apiUrl must be the API origin
API_URL (Helm: tokentimer.config.apiUrl) is the API host origin, e.g.
https://tokentimer.example.com. The dashboard and server append /api for
JSON routes (e.g. /api/auth/features) and /auth for SSO routes themselves.
| Setting | Value |
|---|---|
API_URL env / tokentimer.config.apiUrl | https://tokentimer.example.com |
APP_URL env / tokentimer.config.baseUrl | https://tokentimer.example.com |
| IdP redirect URI (OIDC) | https://tokentimer.example.com/auth/oidc/<slug>/callback |
| IdP ACS (SAML) | https://tokentimer.example.com/auth/saml/<slug>/callback |
Split host (dashboard and API on different origins):
| Setting | Example |
|---|---|
tokentimer.config.baseUrl / APP_URL | https://app.example.com |
tokentimer.config.apiUrl / API_URL | https://api.example.com |
SESSION_COOKIE_DOMAIN | .example.com |
| IdP redirect URI (OIDC) | https://api.example.com/auth/oidc/<slug>/callback |
Ingress routing for that origin:
| Path prefix | Service |
|---|---|
/api/* | API |
/auth/* | API |
/ | dashboard |
Deep links: /auth/oidc/<slug>?next=/dashboard/... and
/auth/saml/<slug>?next=/dashboard/... (OIDC stores next on the session;
SAML sends it as RelayState. Same-origin pathname only, max 512 chars; see
Claim mapping).
Choosing OIDC vs SAML
| Prefer OIDC when | Prefer SAML when |
|---|---|
| IdP supports OIDC well (most cloud IdPs) | Corporate standard is SAML-only |
| You want discovery defaults and minimal claim plumbing | Existing enterprise SAML app template |
| Easier group claims in JWT/userinfo | AD FS or legacy apps without OIDC |
You may register both protocols as separate providers (different slugs) when the license includes both entitlements.
TokenTimer reads groups from configurable claim/attribute paths for role mapping. If the IdP emits no verified groups, bootstrap mappings cannot create them. Keep a local administrator or use a provider that emits the required claims.
Behavior reference (advanced)
How the SSO surface maps to the implementation. You do not need this table for a standard setup; come back to it when debugging or automating.
| Topic | Behaviour |
|---|---|
| Provider registry | Rows in sso_providers and encrypted secrets in sso_provider_secrets |
| Sign-in readiness | /api/auth/features exposes per-provider signInReady. Login buttons use signInReady only. Status badges under Manage SSO providers match this. |
| HA config sync | UI or API writes to sso_providers bump sso_config_version; all API pods reload within SSO_CONFIG_SYNC_INTERVAL_MS (default 45s) or on the next slugged /auth/* request. Bootstrap manifest changes require a pod restart. |
| Callback URLs | Derived from slug + API_URL unless config.callback_url is set on the provider row. Manage SSO providers shows the computed URL with copy-to-clipboard. |
| OIDC discovery | Fetched from ${issuer}/.well-known/openid-configuration (5 min in-process cache per pod). Skip per provider with security.disable_discovery. Keycloak realm issuers get /protocol/openid-connect/* fallbacks when discovery fails. |
| OIDC diagnostics | Admins: Test on the provider under Manage SSO providers, or POST /api/admin/sso/providers/<slug>/test. |
| PKCE | Authorization Code + PKCE S256 when security.pkce is true (default). OIDC session state is in Postgres, not pod memory. |
| HTTPS enforcement | In production (NODE_ENV=production) the OIDC strategy refuses to register if any of issuer / authorization / token / userinfo / JWKS URLs are http://. http://localhost is allowed outside production. |
| SAML InResponseTo | Defaults to always. Postgres table saml_request_cache backs the cache across replicas. Override policy with security.validate_in_response_to=ifPresent or never only when the IdP cannot echo InResponseTo. |
| Default scopes | openid profile email unless config.scopes overrides. |
| Default claim paths | See Claim mapping. |
| New users | Provisioning requires an email claim/attribute on first login; subject-only works for users already linked in external_identities. |
| License gate | /auth/oidc/<slug>* requires feature:oidc; /auth/saml/<slug>* requires feature:saml. |
| Ingress | Route /auth/* to the API service (only the SAML POST callback is CSRF-exempt; session-authenticated writes still require the paired cookie and token). |
Full bootstrap and global env vars: Configuration reference.
Official documentation (starting points)
| Provider | OIDC | SAML |
|---|---|---|
| Microsoft Entra | Register an app, Redirect URI, ID token claims | Enable SAML SSO |
| Okta | Auth code flow, Create app integration | SAML app integration |
| OpenID Connect | Custom SAML app | |
| Auth0 | Regular Web App, OIDC | SAML SP |
| AWS Cognito | User pool as OIDC IdP, App client settings | SAML IdP (Cognito as SP; TokenTimer is SP, so use user pool OIDC) |
| Keycloak | Securing apps | SAML clients |
| AD FS | N/A | Configure RP |