64 docs indexed

Dashboard SSO

Configure OIDC single sign-on (Globus, CILogon, or any discovery-capable provider) for the obleth control-plane dashboard, alongside the break-glass local admin account.

The obleth dashboard supports two authentication methods that coexist: an OIDC SSO path for institutional identity providers and a local email/password admin that always works even when the IdP is unreachable.

Overview

obleth acts as an OIDC Relying Party. When SSO is configured, users sign in via their institution's IdP (Globus, CILogon, or any provider that exposes a discovery document). New SSO users land in a pending state with no access until an admin assigns them a role and a tenant on the dashboard's Users screen.

Two roles are available:

RoleAccess
adminFull dashboard: models, tenants, keys, usage, settings
userSelf-service portal: read-only model list, manage own API keys, view own usage

The local admin bypasses SSO and is always available as a break-glass account.

Configuring OIDC providers

SSO is configured through environment variables on the control plane. Where you set them depends on how you run it:

  • Docker Compose: deploy/docker/.env (read only by the Docker stack).
  • Control plane on the host (npm run dev): control-plane/.env.local.
  • Helm: the controlPlane.* values (see Kubernetes below).

Set the OIDC_PROVIDERS environment variable to a JSON array. Each entry describes one provider:

[
  {
    "providerId": "globus",
    "displayName": "Globus",
    "discoveryUrl": "https://auth.globus.org/.well-known/openid-configuration",
    "clientId": "YOUR_CLIENT_ID",
    "clientSecret": "YOUR_CLIENT_SECRET",
    "scopes": ["openid", "email", "profile"]
  }
]
FieldDescription
providerIdShort identifier used in the callback URL (alphanumeric + hyphens)
displayNameLabel shown on the sign-in button
discoveryUrlOIDC discovery document URL for the provider
clientIdClient ID from the IdP registration
clientSecretClient secret from the IdP registration
scopesOAuth scopes to request; openid email profile is sufficient for obleth

Redirect URI to register with the IdP

When registering an obleth client in your IdP's developer console, use this redirect URI:

https://<dashboard-host>/api/auth/oauth2/callback/<providerId>

For example, with providerId: "globus" and a dashboard at https://dashboard.example.edu:

https://dashboard.example.edu/api/auth/oauth2/callback/globus

Note:

The host in the callback comes from BETTER_AUTH_URL — set it to the external, browser-facing URL of the dashboard (e.g. https://dashboard.example.edu). The redirect URI you register with the IdP must match it exactly, including scheme and port.

Globus

Discovery URL: https://auth.globus.org/.well-known/openid-configuration

Register a confidential client in the Globus Developer Console. Set the redirect URL above as an allowed redirect URI and request the openid email profile scopes.

{
  "providerId": "globus",
  "displayName": "Globus",
  "discoveryUrl": "https://auth.globus.org/.well-known/openid-configuration",
  "clientId": "YOUR_GLOBUS_CLIENT_ID",
  "clientSecret": "YOUR_GLOBUS_CLIENT_SECRET",
  "scopes": ["openid", "email", "profile"]
}

CILogon

Discovery URL: https://cilogon.org/.well-known/openid-configuration

Register a client at cilogon.org/oauth2/register. Set the callback URL and request the openid email profile scopes.

{
  "providerId": "cilogon",
  "displayName": "CILogon",
  "discoveryUrl": "https://cilogon.org/.well-known/openid-configuration",
  "clientId": "cilogon:/client_id/YOUR_ID",
  "clientSecret": "YOUR_CLIENT_SECRET",
  "scopes": ["openid", "email", "profile"]
}

Multiple providers

Pass multiple objects in the array to show a sign-in button for each:

[
  {
    "providerId": "globus",
    "displayName": "Globus",
    "discoveryUrl": "https://auth.globus.org/.well-known/openid-configuration",
    "clientId": "...",
    "clientSecret": "...",
    "scopes": ["openid", "email", "profile"]
  },
  {
    "providerId": "cilogon",
    "displayName": "CILogon",
    "discoveryUrl": "https://cilogon.org/.well-known/openid-configuration",
    "clientId": "...",
    "clientSecret": "...",
    "scopes": ["openid", "email", "profile"]
  }
]

Break-glass local admin

A local email/password admin account is seeded on first boot and always works regardless of IdP availability:

VariableDefaultNotes
DASHBOARD_ADMIN_EMAILadmin@example.comEmail used to sign in
DASHBOARD_PASSWORDMust be at least 8 characters; no default in production

Set both in your deployment's environment (or .env for Docker Compose). The local admin has the admin role and is not subject to the pending-approval flow.

Change DASHBOARD_ADMIN_EMAIL and DASHBOARD_PASSWORD from any dev defaults before exposing the dashboard to a network.

Upgrading from the username login

The dashboard sign-in form now requires an email address, not a username. Two requirements must be met before the break-glass admin can be seeded on the upgraded control plane:

  1. DASHBOARD_ADMIN_EMAIL must be a real email address. A bare value like admin fails validation and no admin account will be created.
  2. DASHBOARD_PASSWORD must be at least 8 characters. A shorter password causes the admin seed to fail; the error is logged and no account is created.

Upgrade steps for an existing install:

  1. Set DASHBOARD_ADMIN_EMAIL to a real email address (e.g. admin@your-org.example).
  2. Ensure DASHBOARD_PASSWORD is 8 or more characters.
  3. Start the upgraded control plane — the admin account is created from those values on first boot (when no admin exists yet).
  4. Sign in at the dashboard with that email and password.

Institutional users who sign in via OIDC SSO are unaffected by this change; they sign in through their provider as before and land in the pending state until an admin assigns them a role and tenant (see Admin approval flow below).

Admin approval flow

When a user signs in via SSO for the first time, their account is created with a pending status. Pending users cannot access the dashboard or the self-service portal.

To grant access:

  1. Sign in as an admin.
  2. Navigate to the Users screen.
  3. Find the pending user and assign them a role (admin or user) and a tenant.
  4. Save — the user can now sign in normally.

Users assigned the user role land in the self-service portal rather than the full dashboard. They can view available models, manage their own API keys, and review their own usage. They cannot see other tenants' data or modify gateway configuration.

Try it locally with Dex

The Docker Compose stack ships a Dex container that acts as a local test identity provider, gated behind the sso-dev profile so it never starts in production. It comes preloaded with one test user:

  • researcher@example.edu / password

Run the whole stack in Docker (default)

The Dex issuer must resolve to the same address from both your browser and the control-plane container. localhost cannot do that — inside the container it points at the container itself, not Dex — so the shipped config uses host.docker.internal, which the compose file gives Dex as a network alias.

Set the profile and provider in deploy/docker/.env (this file is read only by the Docker stack):

COMPOSE_PROFILES=sso-dev
OIDC_PROVIDERS=[{"providerId":"dex","displayName":"Dev SSO (Dex)","discoveryUrl":"http://host.docker.internal:5556/dex/.well-known/openid-configuration","clientId":"obleth-control-plane","clientSecret":"dev-dex-secret","scopes":["openid","email","profile"]}]
cd deploy/docker
docker compose up -d

Open the dashboard at http://localhost:3002 — you'll see a Sign in with Dev SSO (Dex) button. Sign in as the test user above; you'll land on an "awaiting approval" screen (expected — see Admin approval flow). That screen has a Sign out button so you can switch between the break-glass admin and SSO accounts while testing.

Note:

If you change the dashboard's published port from 3002, also update Dex's redirectURIs in deploy/docker/dex/config.docker.yaml and BETTER_AUTH_URL to match.

Run the control plane on the host instead

If you run the control plane on the host with npm run dev (port 3000) while only Dex runs in Docker, both the browser and the Node server reach Dex at plain localhost:5556. Put the config in control-plane/.env.local (the Docker .env is not read in this mode):

# control-plane/.env.local
BETTER_AUTH_URL=http://localhost:3000
OIDC_PROVIDERS=[{"providerId":"dex","displayName":"Dev SSO (Dex)","discoveryUrl":"http://localhost:5556/dex/.well-known/openid-configuration","clientId":"obleth-control-plane","clientSecret":"dev-dex-secret","scopes":["openid","email","profile"]}]
cd deploy/docker && docker compose --profile sso-dev up -d dex postgres
cd ../../control-plane && npm run dev   # dashboard on :3000

Add more local test users

Dex's users live in deploy/docker/dex/config.docker.yaml under staticPasswords. Add an entry (the password is a bcrypt hash) and restart Dex:

node -e "console.log(require('bcryptjs').hashSync('yourpassword',10))"
docker compose -f deploy/docker/docker-compose.yml restart dex

Dex storage is in-memory, so the static list is the source of truth and resets when the container restarts. In production, users live in your real provider (Globus/CILogon) — obleth never creates SSO identities itself, it only records an account the first time someone signs in.

Kubernetes (Helm)

The Helm chart exposes the same settings under controlPlane:

controlPlane:
  # Break-glass admin (always works, even when the IdP is down)
  dashboardAdminEmail: "admin@your-org.example"
  dashboardPassword: ""          # >= 8 chars; supply via --set or an external Secret
  dashboardSessionSecret: ""     # >= 32 chars; reused as BETTER_AUTH_SECRET

  # OIDC SSO (optional; omit or leave empty for break-glass only)
  betterAuthUrl: "https://dashboard.example.com"
  oidcProviders: |
    [{"providerId":"globus","displayName":"Globus","discoveryUrl":"https://auth.globus.org/.well-known/openid-configuration","clientId":"ID","clientSecret":"SECRET","scopes":["openid","email","profile"]}]

For production, store these in a pre-created Secret and reference it with controlPlane.existingSecret. Because the chart then renders no control-plane Secret of its own, that Secret must carry every key: DASHBOARD_PASSWORD, DASHBOARD_SESSION_SECRET, DATABASE_URL, DASHBOARD_ADMIN_EMAIL, BETTER_AUTH_URL, and OIDC_PROVIDERS. See deploy/k8s/obleth/examples/values-production.yaml and the Helm values reference.

Promoting to production (no code change required)

Switching from local Dex to a live Globus or CILogon deployment requires only an environment variable swap — no code change:

  1. Register obleth as a client with your IdP (see above for the redirect URI).
  2. Update OIDC_PROVIDERS to the real discovery URL and client credentials.
  3. Restart the control plane.

Everything else — the approval flow, roles, the break-glass admin — stays the same.