Skip to content

OIDC via Dex

OIDC authentication enables single sign-on through an existing identity provider without distributing credentials or kubeconfigs. Dex is bundled as a Helm subchart and federates upstream providers such as Entra ID, Okta, GitHub, and LDAP.

For Helm values, Dex connectors, static clients, and the tofu login flow, see OIDC Helm Configuration.

Browse Endpoint Authentication Enforcement

When server.oidc.enabled: true and server.anonymousAuth: false, browse API endpoints under /opendepot/ui/v1/* require a valid Authorization: Bearer <token> header. Requests without a valid token return 401 Unauthorized, including requests with an invalid or expired JWT.

A valid token with no matching GroupBinding is accepted and receives public-only visibility. The gate blocks callers with no token or a token that fails all verification paths. When server.anonymousAuth: true, or OIDC is not configured, browse endpoints remain accessible without authentication.

Warning

Set server.anonymousAuth: false together with server.oidc.enabled: true in production to prevent unauthenticated access to the browse API.

OpenDepot Workshop OIDC

When ui.oidc.enabled: true, the UI uses a dedicated Dex client, normally opendepot-ui, separate from the tofu login client opendepot. Set the UI client ID so the Server accepts UI-issued tokens:

ui:
  oidc:
    enabled: true
    clientId: opendepot-ui
    clientSecretName: opendepot-ui-oidc-secret

The Dex opendepot-ui client must include trustedPeers: [opendepot] so Dex includes the Server audience in UI-issued tokens:

dex:
  config:
    staticClients:
      - id: opendepot-ui
        name: OpenDepot UI
        secret: <ui-client-secret>
        trustedPeers:
          - opendepot
        redirectURIs:
          - https://<ui-host>/auth/callback

The UI requires an HTTPS issuer in production. Its discovered authorization, token, and JWKS endpoints must share the configured issuer origin unless an explicit ui.oidc.authzUrl browser override is used. Insecure HTTP endpoints are enabled only when global.developmentMode: true.

The UI also requires NEXT_PUBLIC_BASE_URL to use HTTPS in production. Set global.developmentMode: true only for local development when an HTTP base URL is unavoidable. OIDC state, nonce, and session cookies are HttpOnly and use SameSite=Lax; session cookies are marked Secure for normal deployments.

Note

Without the Server's UI client ID configured, the Stats page and browse endpoints may return empty results for users authenticated through the UI's OIDC client.

GroupBinding Access Control

After OIDC authentication, the Server extracts the JWT groups claim and matches it against GroupBinding expressions. The groups claim is required. Requests with no claim, or with a claim that matches no GroupBinding, receive 403 Forbidden.

Warning

If no GroupBinding resources exist in the Server namespace, all OIDC-authenticated users are denied. Deploy at least one GroupBinding before enabling OIDC in production.

See Fine-Grained Access Control with GroupBinding for expression syntax and setup instructions.

CI/CD with ServiceAccount Fallback

GroupBinding is bypassed for ServiceAccount tokens

When ServiceAccount fallback is enabled, ServiceAccount tokens skip GroupBinding entirely. Kubernetes RBAC is the only access control applied to those tokens. This creates a separate access-control path alongside OIDC.

Before enabling fallback, consider the GitOps workflow for publishing modules or Dex Client Credentials for registry reads that continue to respect GroupBinding.

See CI/CD with ServiceAccount Fallback for setup details and required RBAC.

Troubleshooting

Issue Cause Fix
missing Authorization header .tofurc has no credentials block Add a credentials "host" { token = "..." } block
unauthorized JWT expired or invalid Run tofu login again
Server pod is CrashLoopBackOff Issuer URL is missing or misconfigured Verify server.oidc.issuerUrl and inspect pod logs
Browser opens to an in-cluster hostname Issuer URL was left blank Set both dex.config.issuer and server.oidc.issuerUrl to the external Dex URL
Browser redirects to localhost but fails Dex redirect URI is not registered Add the expected localhost ports to redirectURIs
Stats shows zeroes for UI-authenticated users UI client ID was not propagated to the Server Enable ui.oidc and set a non-empty ui.oidc.clientId