Skip to Content

SSO IdP — single login for every app 🔒

Phase Г.2 of the GA roadmap. Quazzar’s Control Panel runs an embedded OpenID Connect / SAML identity provider (built on dex ) so users sign in once and reach every installed app — Plex, Nextcloud, Grafana, Vaultwarden, or any OIDC-aware service you ship.

Plan: OIDC clients require Orbit Pro. SAML upstream connectors require Enterprise. See Orbit Pro for the full tier matrix.

How it fits together

PlaneRoleLives in
Quazzar IdPIssues OIDC tokens, optionally federates to upstream IdPsCP internal/sso/dex_runner.go
Per-tenant issuerhttps://<cp_host>/oidc/<tenant_id>CP tenant_resolver.go
App OIDC clientOne per installed appCP client_repo.go
Auto-configRenders the client into the app’s env / configOS internal/sso/templates.go + per-app writers

Enabling SSO on an app

  1. Install an app whose template carries an sso_template block (Grafana, Nextcloud, Vaultwarden, Plex, or any template using kind: generic_oidc_env).
  2. Open Settings → SSO on the node, find the app card, click Enable.
  3. Quazzar OS calls POST /api/sso/clients on the CP, receives a fresh client_id + client_secret + the issuer URL, and the per-app writer renders them into the app’s compose env (and where needed a sidecar JSON config under <app_dir>/sso/).
  4. The next time the app starts, your existing Quazzar login takes you straight in.

To pause, toggle Disable — the OS deletes the client from the CP and clears the SSO env on next recreate. The local writer output stays idempotent so re-enable is a no-op for the env block.

Federating to your existing IdP

Pro+ tenants can also flip the IdP into “broker” mode by adding an upstream connector in the CP Settings → SSO view. Supported kinds:

KindPlanNotes
GooglePro+OAuth — paste client_id + secret from Cloud Console.
GitHubPro+OAuth — paste client_id + secret from GitHub Settings → Developer.
Generic OIDCPro+Any OIDC-compliant provider.
SAML 2.0EnterpriseMetadata URL or pasted SP descriptor.
LDAP / ADEnterpriseRead-only bind for legacy directory services.

Once a connector is enabled, the IdP login screen offers it as an option; groups + role mapping flow through to every downstream app via the configured group_claim.

Per-app cap

The cap is plan-based:

TierApp OIDC clientsUpstream connectors
Communityn/a — IdP lockedn/a
Pro≤ 51
Business≤ 253
Enterpriseunlimitedunlimited (SAML included)

When you hit the cap the OS-side useFeature("sso_idp") hook surfaces UpgradePrompt with the next plan up.

API surface

CP-side:

Method + pathPurpose
GET /api/sso/issuerAnnounces the per-tenant issuer URL.
GET /api/sso/clientsLists OIDC clients (secrets stripped).
POST /api/sso/clientsProvisions / refreshes a client.
DELETE /api/sso/clients/{id}Revokes the client.
GET /api/sso/connectorsLists upstream connectors.
POST /api/sso/connectorsAdds an upstream connector.
GET /oidc/{tenant_id}/.well-known/openid-configurationPublic OIDC discovery.

OS-side:

Method + pathPurpose
GET /api/sso/stateLists every app’s SSO state + plan headroom.
GET /api/sso/apps/{app_id}/statusReturns one app’s state.
POST /api/sso/apps/{app_id}/enableWires the app to the IdP.
POST /api/sso/apps/{app_id}/disableTears it down.

All OS paths return HTTP 402 with the canonical upgrade_required body on community tenants — see Orbit Pro for the shape.

Writing your own template

Add an sso_template block to your app’s YAML. Five built-in writers ship today:

sso_template: kind: grafana # or nextcloud, vaultwarden, plex, generic_oidc_env protocol: oidc redirect_uris: - "${app_url}/login/generic_oauth" scopes: [openid, email, profile] # Optional — only used by generic_oidc_env: env_map: OIDC_ISSUER: "${issuer}" OIDC_CLIENT: "${client_id}" OIDC_SECRET: "${client_secret}" OIDC_REDIRECT: "${redirect_uri}"

${app_url} resolves to the public URL of the running app; the ${issuer}, ${client_id}, ${client_secret}, ${redirect_uri} and ${scopes} placeholders are filled from the IdP’s response when the user clicks Enable.