OBLETHdocs

Sign-in and SSO

Let people sign in to the dashboard through your institution's identity provider, next to the break-glass admin account that always works.

4 min readUpdated for v2.4.0Edit on GitHub

Always onA local admin, email and password
SSOAny OIDC provider with a discovery document, such as Globus or CILogon
New usersWait for approval on Users

Set up SSO

  1. 1

    Register the dashboard with your provider

    Create a confidential client in the provider's developer console, requesting the openid email profile scopes, with this redirect URI:

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

    <providerId> is a short name you choose, such as globus.

  2. 2

    Set the dashboard's URL

    Set BETTER_AUTH_URL to the dashboard's browser-facing URL, such as https://dashboard.example.edu. The redirect URI you registered must match it exactly, scheme and port included.

  3. 3

    Add the provider

    Set OIDC_PROVIDERS to a JSON array with one object per 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"]
    }]
    
  4. 4

    Restart the control plane and approve people

    The sign-in page shows a button per provider. Each person's first sign-in creates a pending account; approve it on Users.

Where the variables go depends on how you run the dashboard: deploy/docker/.env for Docker Compose, control-plane/.env.local when running it on the host with npm run dev, or the controlPlane values under Helm (see below). Moving from a test provider to a real one is only a change to these variables.

Provider fields

FieldMeaning
providerIdShort name used in the redirect URI: letters, digits and hyphens
displayNameThe label on the sign-in button
discoveryUrlThe provider's OIDC discovery document
clientId, clientSecretFrom the provider's client registration
scopesDefaults to openid email profile, which is enough
claimsRead email, name or image from a different claim. See below
overrideUserInfotrue re-applies the profile to existing users at every sign-in, not only the first
pkcetrue sends a PKCE challenge, so an intercepted code cannot be redeemed alone. Off by default; turn it on where the provider supports it
authenticationHow the client authenticates at the token endpoint: "post" (credentials in the body, the default) or "basic"

The discovery URLs are https://auth.globus.org/.well-known/openid-configuration for Globus and https://cilogon.org/.well-known/openid-configuration for CILogon (register at cilogon.org/oauth2/register). An unknown authentication value or claims field is refused with an error.

Admin approval flow

A first SSO sign-in creates an account with no access. Pending users see an awaiting-approval screen with Sign out.

On Users, accounts waiting for approval are listed first. Pick a role and a tenant, then Approve; Not now leaves them waiting. Below them, everyone else is listed with what they can reach and when they joined, and can be changed or deactivated.

RoleCan reach
adminThe whole dashboard
userThe self-service portal for their tenant: its models, its keys and its usage. See Tenants and keys

Two changes are always refused, so nobody locks the gateway out: removing admin from your own account, and demoting or deactivating the last active admin. The same rule covers the sign-in library's own admin endpoints under /api/auth/admin/*.

Break-glass local admin

One email-and-password admin is seeded at start-up and works even when the identity provider is down. Public sign-up is closed; every other account comes from a first SSO sign-in.

VariableDefaultNotes
DASHBOARD_ADMIN_EMAILadmin@example.comA real email address
DASHBOARD_PASSWORDnoneAt least 8 characters. Required by Compose and Helm

The account is created only when there is no active admin, so it is made on first boot and not recreated. A password shorter than 8 characters, or an existing non-admin account with the same email, is logged as an error and no account is made; the dashboard still starts and serves SSO. Change both values from any development defaults before the dashboard is reachable from a network.

Reaching the dashboard from another host

Sign-in checks the browser's Origin against BETTER_AUTH_URL, which defaults to http://localhost:3000 (the Compose stack uses http://localhost:3002). Opening the dashboard by IP address or another hostname fails with an invalid-origin error, for the local admin as well as SSO, until you either set BETTER_AUTH_URL to the URL you browse to or list the other origins:

TRUSTED_ORIGINS=http://10.0.0.10:3002,http://obleth.example.internal:3002

A single * trusts every origin: acceptable on a trusted private network, unsafe anywhere reachable from the internet. BETTER_AUTH_URL still sets the host in redirect URIs, whatever TRUSTED_ORIGINS allows.

Kubernetes (Helm)

controlPlane:
  dashboardAdminEmail: "admin@your-org.example"
  dashboardPassword: ""        # at least 8 characters; pass with --set or a Secret
  dashboardSessionSecret: ""   # at least 32 characters; also used as BETTER_AUTH_SECRET
  betterAuthUrl: "https://dashboard.example.edu"
  trustedOrigins: ""
  oidcProviders: |
    [{"providerId":"globus","displayName":"Globus","discoveryUrl":"https://auth.globus.org/.well-known/openid-configuration","clientId":"ID","clientSecret":"SECRET","scopes":["openid","email","profile"]}]

In production, put these in a Secret you create and set controlPlane.existingSecret. The chart then renders no control-plane Secret, so yours must carry every key: DASHBOARD_PASSWORD, DASHBOARD_SESSION_SECRET, BETTER_AUTH_SECRET, DATABASE_URL, DASHBOARD_ADMIN_EMAIL, BETTER_AUTH_URL, TRUSTED_ORIGINS and OIDC_PROVIDERS. See Helm values.

Try it locally with Dex

The Compose stack includes a Dex test provider behind the sso-dev profile, with one user, researcher@example.edu / password. In deploy/docker/.env:

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"]}]

Run docker compose up -d in deploy/docker and open http://localhost:3002. The issuer uses host.docker.internal because it must resolve to the same address from your browser and from the control-plane container. If you change the dashboard's port, update Dex's redirectURIs in deploy/docker/dex/config.docker.yaml and BETTER_AUTH_URL to match.

When the control plane runs on the host (npm run dev, port 3000) and only Dex runs in Docker, use http://localhost:5556/dex/.well-known/openid-configuration as the discovery URL and set BETTER_AUTH_URL=http://localhost:3000 in control-plane/.env.local. More test users go under staticPasswords in the Dex config, with bcrypt-hashed passwords; restart Dex after editing.

Mapping user fields from non-standard claims

Some providers send an email that is not the identifier your institution keys accounts on. Globus, for a university identity, can send a display alias such as Ada.Lovelace@example.edu as email and the canonical alovelace@example.edu as preferred_username. Without a mapping, the same person can arrive as a second, unrecognised user. Map the claim:

{ "claims": { "email": "preferred_username" }, "overrideUserInfo": true, "pkce": true }

A missing, empty or non-string mapped claim falls back to the standard claim. Changing claims.email does not merge existing accounts: a returning user is found by the provider's sub and keeps their original email unless overrideUserInfo is true, so set it when correcting a mapping after users exist.

When the callback fails after a successful login

If the provider's login succeeds and the browser comes back to the dashboard, but the callback then fails, the server-to-server code exchange was refused. Check the provider's token_endpoint_auth_methods_supported and set authentication to match. Only "basic" and "post" are accepted, not the discovery spellings client_secret_basic and client_secret_post. Most providers accept body credentials whatever their discovery document says; Globus advertises only client_secret_basic and still accepts them.