Skip to content

For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.

External OIDC provider

Page as Markdown

Set up your own OpenID Connect identity provider for single sign-on and role-based access control in the Solo UI.

Replace the built-in auto identity provider with your own OpenID Connect (OIDC) identity provider (IdP), so that users log in to the Solo UI with the credentials that you already manage. The steps in this guide apply to any OIDC-compliant provider, such as Keycloak, Okta, or Microsoft Entra ID.

You create two OIDC clients, because the UI backend and the UI frontend have different security requirements. Then, you map your user groups to Solo UI roles and pass both client IDs to the management Helm chart. For background on the login flow and on role mapping, see Identity providers.

Before you begin

  1. Install the Solo UI, and confirm that you can reach it.

  2. Save the external address of the Solo UI as an environment variable. Your IdP redirects users back to this address after they log in.

    export UI_HOST=https://solo-ui.example.com
  3. Confirm that your IdP supports the following capabilities. The Solo UI login flow requires all of them.

    CapabilityUse
    OpenID Connect discoveryThe Solo UI reads ${BACKEND_ISSUER}/.well-known/openid-configuration to find the authorization, token, and logout endpoints.
    Authorization code grantThe login flow for both clients.
    Proof Key for Code Exchange (PKCE), with the S256 challenge methodThe frontend client, which runs in the browser and holds no client secret.
    Client credentials grantThe backend client, for server-to-server calls.
    Refresh tokensRenewing an access token without a new login.
    Custom claims or group mappings in the access tokenMapping your user groups to Solo UI roles.

Create the backend client

The UI backend validates access tokens and serves the Solo UI APIs. Create a confidential client for it, because the backend runs server-side and can hold a client secret.

Tip

Create both Solo UI clients in a dedicated realm, tenant, or authorization server, depending on what your IdP calls this grouping. A separate realm keeps the Solo UI users, groups, and token settings apart from your other applications, so that you change the group claim or the token lifetime for the Solo UI alone.

  1. In your IdP, create a client with the following settings.

    SettingValue
    Client typeConfidential
    Grant typesAuthorization code, client credentials
    Refresh tokensEnabled
    Redirect URI${UI_HOST}/callback
    Post-logout redirect URI${UI_HOST}/logout

    Note

    The Solo UI serves the callback at the root path by default. If you set ui.basePath in the management chart to serve the UI under a subpath, add that subpath to both redirect URIs, such as ${UI_HOST}/solo/callback.

  2. Save the client details as environment variables. You pass these values to the Helm chart later.

    • BACKEND_CLIENT_ID: The client ID of the backend client.
    • BACKEND_CLIENT_SECRET: The client secret of the backend client. Pass this value in the ui.backend.oidc.secret Helm value, and the management chart base64-encodes it and stores it in a Kubernetes secret named ui-backend-oidc-secret. To use your own Kubernetes secret instead, create the secret with the client secret under a clientSecret key, then set ui.backend.oidc.secretRef to the name of your secret and omit ui.backend.oidc.secret.
    • BACKEND_ISSUER: The issuer URL of your IdP, such as https://keycloak.example.com/realms/solo. The Solo UI appends /.well-known/openid-configuration to this URL to discover the authorization, token, and logout endpoints, so the same issuer serves both the backend and the frontend client.
    export BACKEND_CLIENT_ID=<client-id>
    export BACKEND_CLIENT_SECRET=<client-secret>
    export BACKEND_ISSUER=<issuer-url>
  3. Verify that your IdP serves the discovery document at the issuer URL. The Solo UI fails to start the login flow if this request does not return JSON.

    curl -s ${BACKEND_ISSUER}/.well-known/openid-configuration | jq '.authorization_endpoint, .token_endpoint, .end_session_endpoint'

Create the frontend client

The UI frontend runs in the user’s browser. Create a public client for it, so that the login flow uses PKCE instead of a client secret.

  1. In your IdP, create a second client with the following settings.

    SettingValue
    Client typePublic
    Grant typesAuthorization code with PKCE
    PKCE challenge methodS256. The Solo UI sends no other method.
    Refresh tokensEnabled
    Redirect URI${UI_HOST}/callback
    Post-logout redirect URI${UI_HOST}/logout
  2. Save the client ID as an environment variable.

    • FRONTEND_CLIENT_ID: The client ID of the frontend client. The frontend client must have Proof Key for Code Exchange (PKCE) enabled with the S256 challenge method, such as in a Single-Page Application (SPA) type of IdP application.

    The frontend takes no other Helm values. It discovers the authorization, token, and logout endpoints from the same oidc.issuer URL that the backend uses, so you do not collect those endpoints separately.

    export FRONTEND_CLIENT_ID=<client-id>
  3. If your IdP enforces cross-origin resource sharing (CORS) rules, add ${UI_HOST} as an allowed origin. The browser calls the token endpoint directly, so the IdP blocks the login flow without this entry.

Configure the group claim

The UI backend reads a group claim from the access token and maps those groups to Solo UI roles. Configure your IdP to include the claim; otherwise, no user receives a mapped role.

  1. Create groups in your IdP for each level of access that you want to grant. The default Helm values expect the following three group names.

    GroupMapped roleAccess
    adminsglobal.AdminFull access to all resources.
    writersglobal.WriterCreate, read, and update access, but not delete.
    readersglobal.ReaderRead-only access.
  2. Add your users to the groups.

  3. Configure your IdP to return the group memberships in a Groups claim on the access token, not only on the ID token. The UI backend reads the access token. The claim must hold a list of group names, as in the following decoded token.

    {
      "sub": "8a1b...",
      "email": "maria@example.com",
      "Groups": ["admins"]
    }

    Important

    Claim names are case-sensitive, and the default role mapper reads a Groups claim with a capital G. An IdP that returns a lowercase groups claim, such as Microsoft Entra ID, needs a custom rbac.roleMapping.roleMapper expression. Set that value when you connect the IdP to the Solo UI.

  4. Include the claim for the scopes that the Solo UI requests. The UI requests openid, email, and profile, plus any scope that you add in oidc.additionalScopes.

Connect the IdP to the Solo UI

Pass your IdP details to the management Helm chart. The relay chart takes no OIDC values, so you upgrade the management release only.

  1. Get the current values of your management Helm release, so that your upgrade preserves them.

    helm get values solo-management -n solo-enterprise -o yaml > management.yaml
  2. Optional: Create a Kubernetes secret that holds the backend client secret, so that the secret stays out of your Helm values file. The chart reads the value from a clientSecret key, and creates no secret of its own when you set ui.backend.oidc.secretRef in the next step.

    kubectl create secret generic my-oidc-secret -n solo-enterprise --from-literal=clientSecret=${BACKEND_CLIENT_SECRET}
  3. Add the oidc and client values to management.yaml. Setting oidc.issuer to a non-empty value turns off the auto identity provider.

    oidc:
      issuer: ${BACKEND_ISSUER}
    ui:
      backend:
        oidc:
          clientId: ${BACKEND_CLIENT_ID}
          secret: ${BACKEND_CLIENT_SECRET}
      frontend:
        oidc:
          clientId: ${FRONTEND_CLIENT_ID}
    FieldDescription
    oidc.issuerThe issuer URL of your IdP. The Solo UI appends /.well-known/openid-configuration to discover the endpoints. An empty value, which is the default, uses the auto identity provider instead.
    oidc.additionalScopesScopes to request in addition to openid, email, and profile. Add the scope here if your IdP returns the group claim only for a custom scope. Defaults to an empty list.
    ui.backend.oidc.clientIdThe client ID of the backend client. Defaults to kagent-backend, which is a shared-chart default rather than a name to reuse.
    ui.backend.oidc.secretThe client secret of the backend client. The chart base64-encodes this value and stores it in a Kubernetes secret named ui-backend-oidc-secret.
    ui.backend.oidc.secretRefThe name of an existing Kubernetes secret that holds the client secret under a clientSecret key. Use this field instead of ui.backend.oidc.secret to keep the secret out of your Helm values.
    ui.frontend.oidc.clientIdThe client ID of the frontend client. Defaults to kagent-ui, which is a shared-chart default rather than a name to reuse.

    Note

    If you created a Kubernetes secret in the previous step, replace the secret field with secretRef: my-oidc-secret. Set one field or the other, not both.

  4. If your IdP uses a claim name other than Groups, or group names other than admins, writers, and readers, add the role mapping to management.yaml. Each key in roleMappings is a group name from your IdP, and each value is the Solo UI role that the members of that group receive. The roleMapper expression names the claim that holds the groups. The following example reads a lowercase groups claim, and maps the solo-admin, solo-writer, and solo-reader groups to the admin, writer, and reader roles.

    rbac:
      roleMapping:
        roleMapper: "has(claims.groups) ? claims.groups.transformList(i, v, v in rolesMap, rolesMap[v]) : []"
        roleMappings:
          solo-admin: "global.Admin"
          solo-writer: "global.Writer"
          solo-reader: "global.Reader"

    Note

    Set the mapping in the Helm values rather than editing the rbac-config-management ConfigMap. The chart regenerates that ConfigMap on every upgrade, which discards a manual edit.

  5. Upgrade the management release with your values file.

    helm upgrade -i solo-management \
    oci://us-docker.pkg.dev/solo-public/solo-enterprise-helm/charts/management \
    -n solo-enterprise \
    --version 0.5.8 \
    --values management.yaml
  6. Verify that the solo-enterprise-ui pod restarts and reports Running.

    kubectl get pods -n solo-enterprise -l app=solo-enterprise-ui
  7. Confirm that the idp container is gone from the pod. The management chart deploys the auto identity provider only while oidc.issuer is empty, so its absence confirms that the UI now uses your IdP.

    kubectl get pod -n solo-enterprise -l app=solo-enterprise-ui -o jsonpath='{.items[0].spec.containers[*].name}'

    Example output, which no longer includes idp:

    tunnel-server ui-frontend ui-backend k8sobjects-collector

Log in to the Solo UI

  1. Open the Solo UI at ${UI_HOST}. Your browser redirects to the login page of your IdP.

  2. Log in with the credentials of a user in one of your mapped groups.

  3. Confirm that the user has the access that you expect. A user in an admins group can edit resources, and a user in a readers group cannot.

    If a mapped user lands with less access than you expect, check the following items in order.

    CheckHow
    The access token carries the group claimDecode the access token and confirm that the claim name and the group names match your rbac.roleMapping values.
    The claim name matches the role mapperClaim names are case-sensitive. A groups claim does not match the default expression, which reads Groups.
    The ConfigMap holds your mappingRun kubectl get configmap rbac-config-management -n solo-enterprise -o jsonpath='{.data.config}'.
    Stale browser state is not the causeLog in again in a private browser window. The frontend caches tokens, so a stale token survives a chart upgrade.