Skip to content

Register and customize SSO clients

You’ll register an SSO client in Keycloak for an application that handles its own OIDC or SAML authentication flow natively. You’ll also customize the generated Kubernetes secret, add protocol mappers for custom token claims, and configure client attributes.

  • UDS Core deployed
  • UDS CLI installed
  • An application that implements OIDC or SAML natively (handles login redirects, token validation, and session management itself)

When a Package CR declares an sso block, the UDS Operator:

  1. Creates a Keycloak client in the uds realm
  2. Stores the client credentials in a Kubernetes secret named sso-client-<clientId> in the application namespace
  3. For SAML clients, fetches the IdP signing certificate from Keycloak and includes it in the secret as samlIdpCertificate

The application reads its credentials from this secret and speaks directly to Keycloak. There is no proxy layer involved.

If your application expects credentials in a specific format (JSON config file, properties file, etc.), you can use secretConfig.template to control the secret layout.

  1. Register the SSO client in a Package CR

    Choose the protocol supported by your application. If your application supports both, UDS package requirements recommend considering SAML with SCIM as the more secure default.

    Define an OIDC client with redirectUris pointing to your application’s callback endpoint:

    package.yaml
    apiVersion: uds.dev/v1alpha1
    kind: Package
    metadata:
    name: my-app
    namespace: my-app
    spec:
    sso:
    - name: My Application
    clientId: my-app
    redirectUris:
    - "https://my-app.uds.dev/auth/callback"
    defaultClientScopes:
    - openid

    The operator creates a confidential OIDC client in Keycloak and stores all client credentials in a Kubernetes secret named sso-client-my-app.

  2. (Optional) Customize the generated Kubernetes secret

    By default, the secret contains every Keycloak client field as a separate key. Use secretConfig to control the secret name, add labels and annotations, and template the data layout. Each key in template becomes a key in the Kubernetes secret; include only the keys your application needs:

    package.yaml
    # This example shows multiple output formats for illustration.
    # In practice, include only the format(s) your application expects.
    spec:
    sso:
    - name: My Application
    clientId: my-app
    redirectUris:
    - "https://my-app.uds.dev/auth/callback"
    secretConfig:
    name: my-app-oidc-credentials
    labels:
    app.kubernetes.io/part-of: my-app
    template:
    # Raw key-value pairs (useful for envFrom)
    CLIENT_ID: "clientField(clientId)"
    CLIENT_SECRET: "clientField(secret)"
    # JSON config file
    config.json: |
    {
    "client_id": "clientField(clientId)",
    "client_secret": "clientField(secret)",
    "defaultScopes": clientField(defaultClientScopes).json(),
    "redirect_uri": "clientField(redirectUris)[0]"
    }
    # Properties file
    auth.properties: |
    client-id=clientField(clientId)
    client-secret=clientField(secret)
    redirect-uri=clientField(redirectUris)[0]
    # YAML config file
    auth.yaml: |
    client_id: clientField(clientId)
    client_secret: clientField(secret)
    default_scopes: clientField(defaultClientScopes).json()
    redirect_uri: clientField(redirectUris)[0]

    The clientField() syntax references Keycloak client properties. Supported operations:

    SyntaxResult
    clientField(clientId)Raw string value of the field
    clientField(redirectUris).json()JSON-serialized value (for arrays and objects)
    clientField(redirectUris)[0]Single element from an array or object by key
  3. (Optional) Add protocol mappers for custom token claims

    Protocol mappers control what claims appear in tokens issued for this client. Add mappers to the protocolMappers array:

    Add an aud claim so tokens are accepted by a specific target application:

    package.yaml
    spec:
    sso:
    - name: My Application
    clientId: my-app
    redirectUris:
    - "https://my-app.uds.dev/auth/callback"
    protocolMappers:
    - name: target-audience
    protocol: "openid-connect"
    protocolMapper: "oidc-audience-mapper"
    config:
    included.client.audience: "target-app-client-id"
    access.token.claim: "true"
    introspection.token.claim: "true"
    id.token.claim: "false"
    lightweight.claim: "false"
    userinfo.token.claim: "false"
  4. (Optional) Configure client attributes

    The attributes map sets Keycloak client-level properties. Only a validated subset is accepted by the operator:

    package.yaml
    spec:
    sso:
    - name: My Application
    clientId: my-app
    redirectUris:
    - "https://my-app.uds.dev/auth/callback"
    attributes:
    access.token.lifespan: "300"
    pkce.code.challenge.method: "S256"
    post.logout.redirect.uris: "https://my-app.uds.dev/logged-out"
    use.refresh.tokens: "true"

    Supported OIDC attributes:

    AttributeDescription
    access.token.lifespanOverride the realm-level token lifespan (seconds)
    client.session.idle.timeoutClient-specific session idle timeout (seconds)
    client.session.max.lifespanClient-specific maximum session lifespan (seconds)
    pkce.code.challenge.methodRequire PKCE (S256 or plain)
    post.logout.redirect.urisAllowed post-logout redirect URIs
    use.refresh.tokensEnable refresh tokens ("true" / "false")
    logout.confirmation.enabledShow logout confirmation page (defaults to "true")
    backchannel.logout.session.requiredInclude session ID in backchannel logout ("true" / "false")
    backchannel.logout.revoke.offline.tokensRevoke offline tokens on backchannel logout ("true" / "false")
    oauth2.device.authorization.grant.enabledEnable the device authorization grant ("true" / "false")
    oidc.ciba.grant.enabledEnable the CIBA grant ("true" / "false")
  5. Deploy the Package CR

    (Recommended) Include the Package CR in your Zarf package and create/deploy. See Packaging applications for general packaging guidance.

    Terminal window
    uds zarf package create --confirm
    uds zarf package deploy zarf-package-*.tar.zst --confirm

    Or apply the Package CR directly for quick testing:

    Terminal window
    uds zarf tools kubectl apply -f package.yaml
  6. Configure your application to use the client credentials

    Review your application’s documentation for how to configure SSO. Point it at the generated Kubernetes secret (sso-client-<clientId> by default, or secretConfig.name if set) to supply the client ID, client secret, and issuer URL (https://sso.<domain>/realms/uds). For SAML clients, the secret also includes the samlIdpCertificate.

Confirm the client was created and the secret is available:

Terminal window
# Check that the `Package` CR was reconciled
uds zarf tools kubectl get package my-app -n my-app
# Verify the client secret exists
uds zarf tools kubectl get secret -n my-app sso-client-my-app

Verify the Keycloak client:

  1. Log in to the Keycloak admin console (uds realm)
  2. Go to Clients and find your client ID
  3. Confirm the protocol, redirect URIs, and client settings match your Package CR

End-to-end test (OIDC):

  1. Navigate to your application’s URL in a browser
  2. The application should redirect you to Keycloak for login
  3. After authenticating, you should be redirected back to the application’s callback URI

End-to-end test (SAML):

  1. Navigate to your application’s SSO login URL
  2. The application should redirect you to Keycloak’s SAML login page
  3. After authenticating, Keycloak should POST a SAML assertion back to your application’s callback URL

Inspect the generated secret:

Terminal window
# View all keys in the secret
uds zarf tools kubectl get secret -n my-app sso-client-my-app -o jsonpath='{.data}' | jq 'keys'
# Retrieve the client secret value
# Linux
uds zarf tools kubectl get secret -n my-app sso-client-my-app -o jsonpath='{.data.secret}' | base64 -d
# macOS
uds zarf tools kubectl get secret -n my-app sso-client-my-app -o jsonpath='{.data.secret}' | base64 -D

Problem: Package CR rejected with “must specify redirectUris”

Section titled “Problem: Package CR rejected with “must specify redirectUris””

Symptom: kubectl apply fails with a validation error about missing redirect URIs.

Solution: standardFlowEnabled defaults to true, which requires redirectUris. Either add redirect URIs or explicitly set standardFlowEnabled: false if your client does not need redirect URI validation (e.g., IdP-initiated SAML clients, service account clients).

Problem: Package CR rejected with “unsupported attribute”

Section titled “Problem: Package CR rejected with “unsupported attribute””

Symptom: The operator denies the Package CR because of an unrecognized attribute key.

Solution: Only a specific set of attributes is allowed. Check the attribute name for typos and verify it is in the supported list above. Custom Keycloak attributes that are not in the validated set cannot be set via the Package CR. Use OpenTofu for post-deploy management of unsupported attributes.

Problem: Client secret not found in the namespace

Section titled “Problem: Client secret not found in the namespace”

Symptom: The expected Kubernetes secret does not exist after applying the Package CR.

Solution: Check the UDS Operator logs for errors:

Terminal window
uds zarf tools kubectl logs -n pepr-system -l app=pepr-uds-core-watcher --tail=50 | grep <client-id>

If you specified secretConfig.name, the secret uses that name instead of the default sso-client-<clientId>.

Problem: SAML IdP certificate missing from secret

Section titled “Problem: SAML IdP certificate missing from secret”

Symptom: The samlIdpCertificate key is empty or missing in the generated secret.

Solution: The operator fetches the certificate from Keycloak’s SAML descriptor endpoint at http://keycloak-http.keycloak.svc.cluster.local:8080/realms/uds/protocol/saml/descriptor. If Keycloak is not ready or the endpoint is unreachable, the certificate will be empty. Verify Keycloak is healthy:

Terminal window
uds zarf tools kubectl get pods -n keycloak -l app.kubernetes.io/name=keycloak