Skip to main content

Single sign-on (OIDC)

sol2docker supports OpenID Connect single sign-on using the Authorization Code flow, alongside (not instead of) local login. It's dependency-free: discovery and token exchange happen over plain fetch, and id_token signatures are verified with Node's built-in crypto (JWKS → RS256).

When SSO is configured, the login screen shows a Sign in with ‹provider› button next to the username/password form. First-time users are auto-provisioned, and their roles are synced from an IdP groups claim on every login.

Enabling it

SSO is configured entirely through environment variables — the client secret never touches the database. It turns on automatically once the three required variables are set; if any is missing, SSO stays off and only local login is offered.

VariableRequiredDefaultPurpose
SOL2DOCKER_OIDC_ISSUERIssuer URL. sol2docker fetches ‹issuer›/.well-known/openid-configuration to discover the authorization, token, and JWKS endpoints. A trailing slash is trimmed.
SOL2DOCKER_OIDC_CLIENT_IDClient ID registered with your IdP.
SOL2DOCKER_OIDC_CLIENT_SECRETClient secret for the confidential client.
SOL2DOCKER_OIDC_REDIRECT_URIderivedExplicit callback URL. If unset, it's derived per request as ‹scheme›://‹host›/api/v2/auth/oidc/callback. Set it when sol2docker sits behind a proxy that rewrites scheme/host.
SOL2DOCKER_OIDC_SCOPESopenid profile emailSpace-separated scopes. Add whatever scope your IdP needs to include the groups claim.
SOL2DOCKER_OIDC_PROVIDER_NAMESSOLabel shown on the login button and used to namespace SSO usernames without a verified email.
SOL2DOCKER_OIDC_DEFAULT_ROLE(none)Fallback global role for an SSO user when no group mapping matches (re-evaluated each login). One of the built-in role ids — platform_admin, platform_operator, environment_operator, environment_viewer — or a custom role id. See Built-in roles.
SOL2DOCKER_OIDC_GROUPS_CLAIMgroupsDot-path to the groups/roles array in the id_token. Supports nested claims, e.g. resource_access.‹client›.roles (Keycloak client roles). See Group → role mapping.
SOL2DOCKER_OIDC_LOGOUT_URLdiscoveredExplicit RP-initiated logout endpoint, used when the provider's discovery document has no end_session_endpoint. See Signing out.
SOL2DOCKER_OIDC_ADOPT_BY_USERNAMEfalsetrue lets a first SSO login adopt an existing account whose username equals the verified email (to migrate pre-existing local accounts). Off by default — see How accounts are provisioned.
SOL2DOCKER_DISABLE_LOCAL_LOGINfalsetrue turns off username/password login entirely — the login page redirects straight to the provider, and local accounts (including their API tokens and existing sessions) are locked out. Only honored when OIDC is configured. See OIDC-only login.
services:
sol2docker:
image: ghcr.io/sol2docker/sol2docker:beta
environment:
SOL2DOCKER_OIDC_ISSUER: "https://id.example.com/realms/main"
SOL2DOCKER_OIDC_CLIENT_ID: "sol2docker"
SOL2DOCKER_OIDC_CLIENT_SECRET: "‹client-secret›"
SOL2DOCKER_OIDC_PROVIDER_NAME: "Company SSO"
SOL2DOCKER_OIDC_SCOPES: "openid profile email roles"
SOL2DOCKER_OIDC_GROUPS_CLAIM: "resource_access.sol2docker.roles"
SOL2DOCKER_OIDC_DEFAULT_ROLE: "environment_viewer"

Register the redirect URI

At your identity provider, register sol2docker as a confidential client (Authorization Code flow) and add the redirect / callback URL:

https://‹your-sol2docker-host›/api/v2/auth/oidc/callback

This must match exactly. If you set SOL2DOCKER_OIDC_REDIRECT_URI, register that value; otherwise sol2docker builds the callback from its public base URL (see below).

Behind a reverse proxy

A TLS-terminating proxy forwards requests to sol2docker as plain http, so any URL derived from the request — the OIDC callback and the post-logout redirect — would come out http://… with the internal host. Set SOL2DOCKER_PUBLIC_URL to the address users actually reach sol2docker at (e.g. https://sol2docker.example.com) and those URLs are built from it. You can still pin the callback specifically with SOL2DOCKER_OIDC_REDIRECT_URI.

How accounts are provisioned

On a successful login sol2docker verifies the id_token (issuer, audience, expiry, nonce, and RS256 signature against the provider's JWKS), then finds or creates the account.

Accounts are keyed by the immutable sub. sol2docker stores the provider sub on the local account and matches by it first, so an email change at the IdP updates the same account rather than creating a duplicate. A first-time identity is otherwise created fresh — it does not silently adopt an existing account that happens to share the username, since a weakly-verified email could then take over that account. To migrate pre-existing (hand-provisioned or pre-sub) local accounts, set SOL2DOCKER_OIDC_ADOPT_BY_USERNAME=true to allow a one-time username match. The username is:

  • Verified email — if the token has email_verified: true and an email, the username is that email.
  • Everything else — namespaced by the sub as ‹provider-name›:‹sub›.
Why unverified claims don't select accounts

email and preferred_username are mutable at many IdPs. Allowing an unverified claim to match an existing account would let anyone who can set their own email take over that user, so only a verified email maps to a plain username. Everything else is tied to sub.

The IdP owns the profile. On every login sol2docker syncs the display name (and, for verified emails, the username) from the token onto the local copy — the provider is the source of truth. Because of that, a provider-backed account's password and two-factor are managed at the IdP, not in sol2docker: the My Account page hides those controls, and the password/2FA/reset endpoints refuse changes for SSO accounts. Roles are still managed in sol2docker (locally or via group mapping).

First-time users are created automatically. If SOL2DOCKER_OIDC_DEFAULT_ROLE is set, they receive that global role as a fallback — it's applied only when no group mapping matches, and is re-evaluated on every login, so it drops away automatically once a mapping grants a role. Without a default role and with no matching mapping, a user starts with no access until an admin (or a group mapping) grants one.

OIDC-only login

Set SOL2DOCKER_DISABLE_LOCAL_LOGIN=true to turn off username/password login entirely. The login page then redirects straight to the provider — no local form is shown — the /auth/login endpoint refuses password logins, and local-origin accounts are locked out through every path (their existing sessions and API tokens stop working too, not just the form). It is only honored when OIDC is configured; otherwise it's ignored (with a startup warning) so you can't lock everyone out.

Avoid locking yourself out

With local login off, the first-boot admin account can no longer sign in. Before enabling this, make sure an SSO user will get the platform_admin role — via SOL2DOCKER_OIDC_DEFAULT_ROLE=platform_admin or a group mapping — or you will have no way in. There is no local break-glass.

Group → role mapping

sol2docker reads a groups claim and re-syncs the user's SSO-origin role assignments on every login (both granting and revoking, so removing someone from an IdP group removes the corresponding access next time they sign in).

By default it reads the top-level groups claim. Point SOL2DOCKER_OIDC_GROUPS_CLAIM at a different claim — including a nested one — when your provider puts roles elsewhere. The value is a dot-path walked into the token; an array is used as-is, and a single string (optionally space/comma-separated) is split into entries. For Keycloak client roles, for example:

SOL2DOCKER_OIDC_GROUPS_CLAIM="resource_access.sol2docker.roles"

resolves resource_access.sol2docker.roles["admin", "viewer", …].

The claim is looked for in all three places a provider might carry it — the id_token, the access token, and the userinfo response — and the results are unioned. This matters for Keycloak, which by default puts resource_access.<client>.roles in the access token, not the id_token: you don't have to also enable "Add to ID token" on the mapper for mapping to work.

Map groups to sol2docker roles in the UI under Roles → OIDC mappings:

  • Each mapping is group name → role, with an optional environment scope (leave it global to apply everywhere). The target is any built-in role — platform_admin, platform_operator, environment_operator, environment_viewer — or a custom role; see Built-in roles.
  • Managing mappings requires the users.write capability.
  • Mappings are additive — a user gets the union of every role their groups map to.

Make sure the scope you configured in SOL2DOCKER_OIDC_SCOPES actually causes your IdP to emit the groups claim (many providers gate it behind a dedicated scope or a client mapper).

note

Roles granted by group mappings and by SOL2DOCKER_OIDC_DEFAULT_ROLE are server-enforced, exactly like locally assigned roles — see Users & RBAC.

Signing out

When an SSO user signs out, sol2docker revokes the local session and, if the provider advertises an end_session_endpoint, sends the browser through the provider's RP-initiated logout so they're signed out at the identity provider too — otherwise the next sign-in would silently reuse the still-open IdP session. The provider then returns the browser to sol2docker's login screen.

For this to complete cleanly, register sol2docker's origin as a post-logout redirect URI at your IdP (sol2docker sends ‹your-sol2docker-host›/):

https://‹your-sol2docker-host›/

If the provider doesn't advertise an end_session_endpoint in its discovery document, set SOL2DOCKER_OIDC_LOGOUT_URL to the logout endpoint explicitly and sol2docker will use it (still appending client_id and the post-logout redirect). With neither available, sign-out is local only — the sol2docker session is still revoked. Locally-created accounts always sign out locally.

Troubleshooting

If SSO fails, sol2docker renders a small error page with the reason. Common cases:

SymptomLikely cause
No SSO button on the login screenOne of the three required variables is unset — SSO stays off. Check the server log for OIDC enabled — provider … on boot.
redirect_uri mismatch at the IdPThe registered callback doesn't match. Pin SOL2DOCKER_OIDC_REDIRECT_URI, or fix proxy scheme/host forwarding.
id_token issuer mismatchSOL2DOCKER_OIDC_ISSUER doesn't equal the iss in the token. Use the exact issuer the discovery document reports.
Unsupported id_token algOnly RS256-signed id_tokens are accepted. Configure the client to sign with RS256.
User signs in but has no accessNo DEFAULT_ROLE and no matching group mapping. Set a default role or add a Roles → OIDC mapping.
Group mappings never applyThe groups claim isn't in the token — add the required scope/mapper at the IdP so groups (or roles) is emitted.