Skip to main content
Version: next

SSO identity provider guides

Audience: identity provider (IdP) administrators choosing and wiring a provider (Entra ID, Okta, Keycloak, ...) for TokenTimer Enterprise.

TokenTimer Enterprise supports multiple OpenID Connect and SAML 2.0 providers side by side. In the dashboard, open System Settings → Manage SSO providers (the UI may label this SSO Providers). Each provider has a stable slug (for example entra, keycloak, okta). Use a short IdP name; do not put -oidc or -saml in the slug (the type field already selects the protocol). When one vendor runs both protocols, use two distinct slugs (e.g. keycloak and keycloak2). The login page shows one button per enabled, entitled provider (GET /api/auth/features).

Quickstarts:

Sign-in flow at a glance

PKCE (Proof Key for Code Exchange) is an OIDC hardening step, enabled by default.

Provider guides

ProviderProtocolsGuide
KeycloakOIDC (SAML optional)Keycloak
Microsoft Entra IDOIDC, SAMLMicrosoft Entra ID
OktaOIDC, SAMLOkta
Google WorkspaceOIDCGoogle Workspace
Auth0OIDCAuth0
Amazon CognitoOIDCAmazon Cognito
Active Directory Federation Services (AD FS)SAMLAD FS

Shared references:

Configuration surfaces

SurfaceUse for
System Settings → Manage SSO providersCreate, edit, test, and enable providers; copy slug-scoped callback URLs
System Settings → Single Sign-OnIdP group to TokenTimer role bindings
Helm ssoProviders[]GitOps bootstrap; chart renders sso-providers.json ConfigMap
Compose sso-providers.jsonLocal/bootstrap manifest (bind-mounted at /etc/tokentimer/sso-providers.json)
SSO_<SLUG>_* env varsSecret material and GitOps field locks per provider (see Configuration reference)

Prerequisites (all providers)

RequirementNotes
Enterprise licensefeature:oidc and/or feature:saml on the JWT
Public API URLMust match what browsers and the IdP use for callbacks (API_URL)
HTTPS in productionSession cookies expect TLS (APP_URL, API_URL)
TRUST_PROXY_HOPSMatch your ingress/LB chain (default 2). Wrong values break callback URLs behind TLS
IngressRoute /auth/* to the API service, not only /api/*
Secrets keyringAn encryption keyring for SSO secrets. Helm auto-creates <release>-enterprise-secrets by default; Compose uses SSO_SECRETS_KEYRING_FILE. To supply your own, follow the Keyring JSON format section in the Configuration reference

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.

PurposeOIDCSAML
Browser sign-in startGET /auth/oidc/<slug>GET /auth/saml/<slug>
Callback (register in IdP)GET /auth/oidc/<slug>/callbackPOST /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/aGET {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.

SettingValue
API_URL env / tokentimer.config.apiUrlhttps://tokentimer.example.com
APP_URL env / tokentimer.config.baseUrlhttps://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):

SettingExample
tokentimer.config.baseUrl / APP_URLhttps://app.example.com
tokentimer.config.apiUrl / API_URLhttps://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 prefixService
/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 whenPrefer SAML when
IdP supports OIDC well (most cloud IdPs)Corporate standard is SAML-only
You want discovery defaults and minimal claim plumbingExisting enterprise SAML app template
Easier group claims in JWT/userinfoAD 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 cannot emit groups in the token, use bootstrap admin group mappings in the provider row or fix IdP token configuration.

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.

TopicBehaviour
Provider registryRows 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 syncUI 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 URLsDerived 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 discoveryFetched 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 diagnosticsAdmins: Test on the provider under Manage SSO providers, or POST /api/admin/sso/providers/<slug>/test.
PKCEAuthorization Code + PKCE S256 when security.pkce is true (default). OIDC session state is in Postgres, not pod memory.
HTTPS enforcementIn 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 InResponseToDefaults 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 scopesopenid profile email unless config.scopes overrides.
Default claim pathsSee Claim mapping.
New usersProvisioning 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.
IngressRoute /auth/* to the API service (CSRF is exempt for these paths).

Full bootstrap and global env vars: Configuration reference.

Official documentation (starting points)

ProviderOIDCSAML
Microsoft EntraRegister an app, Redirect URI, ID token claimsEnable SAML SSO
OktaAuth code flow, Create app integrationSAML app integration
GoogleOpenID ConnectN/A (use OIDC or a third-party SAML bridge)
Auth0Regular Web App, OIDCSAML SP
AWS CognitoUser pool as OIDC IdP, App client settingsSAML IdP (Cognito as SP; TokenTimer is SP, so use user pool OIDC)
KeycloakSecuring appsSAML clients
AD FSN/AConfigure RP