Register and customize SSO clients
What you’ll accomplish
Section titled “What you’ll accomplish”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.
Prerequisites
Section titled “Prerequisites”- UDS Core deployed
- UDS CLI installed
- An application that implements OIDC or SAML natively (handles login redirects, token validation, and session management itself)
Before you begin
Section titled “Before you begin”When a Package CR declares an sso block, the UDS Operator:
- Creates a Keycloak client in the
udsrealm - Stores the client credentials in a Kubernetes secret named
sso-client-<clientId>in the application namespace - 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.
-
Register the SSO client in a
PackageCRChoose 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
redirectUrispointing to your application’s callback endpoint:package.yaml apiVersion: uds.dev/v1alpha1kind: Packagemetadata:name: my-appnamespace: my-appspec:sso:- name: My ApplicationclientId: my-appredirectUris:- "https://my-app.uds.dev/auth/callback"defaultClientScopes:- openidThe operator creates a confidential OIDC client in Keycloak and stores all client credentials in a Kubernetes secret named
sso-client-my-app.Set
protocol: "saml"and provideredirectUrispointing to your application’s SAML callback:package.yaml apiVersion: uds.dev/v1alpha1kind: Packagemetadata:name: my-saml-appnamespace: my-saml-appspec:sso:- name: My SAML ApplicationclientId: my-saml-appprotocol: "saml"redirectUris:- "https://my-saml-app.uds.dev/auth/saml/callback"attributes:saml.client.signature: "false"The operator creates a SAML client in Keycloak and includes the IdP signing certificate as
samlIdpCertificatein the generated Kubernetes secret. Your application uses this certificate to validate SAML assertions from Keycloak.You can configure additional SAML behavior through the
attributesblock. Supported SAML attributes:Attribute Description saml_assertion_consumer_url_postPOST binding URL for receiving SAML assertions saml_assertion_consumer_url_redirectRedirect binding URL for receiving SAML assertions saml_single_logout_service_url_postPOST binding URL for single logout saml_single_logout_service_url_redirectRedirect binding URL for single logout saml_idp_initiated_sso_url_nameURL fragment for IdP-initiated SSO saml_name_id_formatNameID format ( username,email,transient,persistent)saml.assertion.signatureSign SAML assertions ( "true"/"false")saml.client.signatureRequire client-signed requests ( "true"/"false")saml.encryptEncrypt SAML assertions ( "true"/"false")saml.signing.certificateClient signing certificate (PEM, no header/footer) -
(Optional) Customize the generated Kubernetes secret
By default, the secret contains every Keycloak client field as a separate key. Use
secretConfigto control the secret name, add labels and annotations, and template the data layout. Each key intemplatebecomes 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 ApplicationclientId: my-appredirectUris:- "https://my-app.uds.dev/auth/callback"secretConfig:name: my-app-oidc-credentialslabels:app.kubernetes.io/part-of: my-apptemplate:# Raw key-value pairs (useful for envFrom)CLIENT_ID: "clientField(clientId)"CLIENT_SECRET: "clientField(secret)"# JSON config fileconfig.json: |{"client_id": "clientField(clientId)","client_secret": "clientField(secret)","defaultScopes": clientField(defaultClientScopes).json(),"redirect_uri": "clientField(redirectUris)[0]"}# Properties fileauth.properties: |client-id=clientField(clientId)client-secret=clientField(secret)redirect-uri=clientField(redirectUris)[0]# YAML config fileauth.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:Syntax Result 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 -
(Optional) Add protocol mappers for custom token claims
Protocol mappers control what claims appear in tokens issued for this client. Add mappers to the
protocolMappersarray:Add an
audclaim so tokens are accepted by a specific target application:package.yaml spec:sso:- name: My ApplicationclientId: my-appredirectUris:- "https://my-app.uds.dev/auth/callback"protocolMappers:- name: target-audienceprotocol: "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"Map a Keycloak user attribute into a token claim:
package.yaml spec:sso:- name: My ApplicationclientId: my-appredirectUris:- "https://my-app.uds.dev/auth/callback"protocolMappers:- name: departmentprotocol: "openid-connect"protocolMapper: "oidc-usermodel-attribute-mapper"config:user.attribute: "department"claim.name: "department"access.token.claim: "true"id.token.claim: "true"userinfo.token.claim: "true"jsonType.label: "String"Include the user’s Keycloak group memberships in the token:
package.yaml spec:sso:- name: My ApplicationclientId: my-appredirectUris:- "https://my-app.uds.dev/auth/callback"protocolMappers:- name: group-membershipprotocol: "openid-connect"protocolMapper: "oidc-group-membership-mapper"config:claim.name: "groups"full.path: "true"access.token.claim: "true"id.token.claim: "true"userinfo.token.claim: "true" -
(Optional) Configure client attributes
The
attributesmap sets Keycloak client-level properties. Only a validated subset is accepted by the operator:package.yaml spec:sso:- name: My ApplicationclientId: my-appredirectUris:- "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:
Attribute Description 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 ( S256orplain)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") -
Deploy the
PackageCR(Recommended) Include the
PackageCR in your Zarf package and create/deploy. See Packaging applications for general packaging guidance.Terminal window uds zarf package create --confirmuds zarf package deploy zarf-package-*.tar.zst --confirmOr apply the
PackageCR directly for quick testing:Terminal window uds zarf tools kubectl apply -f package.yaml -
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, orsecretConfig.nameif set) to supply the client ID, client secret, and issuer URL (https://sso.<domain>/realms/uds). For SAML clients, the secret also includes thesamlIdpCertificate.
Verification
Section titled “Verification”Confirm the client was created and the secret is available:
# Check that the `Package` CR was reconcileduds zarf tools kubectl get package my-app -n my-app
# Verify the client secret existsuds zarf tools kubectl get secret -n my-app sso-client-my-appVerify the Keycloak client:
- Log in to the Keycloak admin console (uds realm)
- Go to Clients and find your client ID
- Confirm the protocol, redirect URIs, and client settings match your
PackageCR
End-to-end test (OIDC):
- Navigate to your application’s URL in a browser
- The application should redirect you to Keycloak for login
- After authenticating, you should be redirected back to the application’s callback URI
End-to-end test (SAML):
- Navigate to your application’s SSO login URL
- The application should redirect you to Keycloak’s SAML login page
- After authenticating, Keycloak should POST a SAML assertion back to your application’s callback URL
Inspect the generated secret:
# View all keys in the secretuds zarf tools kubectl get secret -n my-app sso-client-my-app -o jsonpath='{.data}' | jq 'keys'
# Retrieve the client secret value# Linuxuds zarf tools kubectl get secret -n my-app sso-client-my-app -o jsonpath='{.data.secret}' | base64 -d# macOSuds zarf tools kubectl get secret -n my-app sso-client-my-app -o jsonpath='{.data.secret}' | base64 -DTroubleshooting
Section titled “Troubleshooting”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:
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:
uds zarf tools kubectl get pods -n keycloak -l app.kubernetes.io/name=keycloakRelated documentation
Section titled “Related documentation”PackageCR reference - full SSO field specification- Identity & Authorization reference - realm initialization variables and authentication flow configuration
- Keycloak Admin REST API - upstream client management API
- Identity & Authorization concepts - background on native SSO vs Authservice
- Enforce group-based access controls - restrict which Keycloak groups can access your application
- Configure automatic pod reload - restart pods automatically when SSO client secrets are rotated
- Configure service account clients - set up machine-to-machine authentication for automated processes