KeycloakClient CRD Reference¶
Complete reference for the KeycloakClient Custom Resource Definition.
Overview¶
The KeycloakClient CRD defines a Keycloak client - an OAuth2/OIDC application that uses Keycloak for authentication and authorization. Clients can be web applications, mobile apps, APIs, or service-to-service integrations.
API Version: vriesdemichael.github.io/v1
Kind: KeycloakClient
Plural: keycloakclients
Singular: keycloakclient
Short Names: kcc
Minimal Example¶
# yaml-language-server: $schema=https://vriesdemichael.github.io/keycloak-operator/schemas/v1/KeycloakClient.json
apiVersion: vriesdemichael.github.io/v1
kind: KeycloakClient
metadata:
name: my-app
namespace: my-team
spec:
clientId: my-app
realmRef:
name: my-realm
namespace: my-team
Spec Fields¶
Core Configuration¶
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
clientId |
string |
Yes | - | Unique client identifier (1-255 characters) |
clientName |
string |
No | - | Human-readable client name |
description |
string |
No | - | Client description |
Example:
spec:
clientId: webapp-production
clientName: "Production Web Application"
description: "Customer-facing web application"
Realm Reference (Required)¶
Reference to the parent KeycloakRealm and authorization token.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
realmRef.name |
string |
Yes | - | Name of the KeycloakRealm CR |
realmRef.namespace |
string |
Yes | - | Namespace of the KeycloakRealm CR |
Example:
Client Type Configuration¶
Configure the basic client type and protocol.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
publicClient |
boolean | No | false |
Whether this is a public client (no client secret). Use true for SPAs and mobile apps. |
bearerOnly |
boolean | No | false |
Bearer-only client (for APIs that only verify tokens, don't initiate login) |
protocol |
string |
No | openid-connect |
Client protocol. Options: openid-connect, saml, docker-v2 |
Client Types:
- Confidential Client (
publicClient: false): Server-side applications that can securely store client secrets (traditional web apps, backend services) - Public Client (
publicClient: true): Applications that cannot securely store secrets (SPAs, mobile apps, CLIs) - Bearer-Only (
bearerOnly: true): APIs that only validate tokens, don't initiate login flows
Example - Confidential:
Example - Public:
Example - Bearer-Only:
OAuth2/OIDC Configuration¶
Configure redirect URIs and web origins for OAuth2/OIDC flows.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
redirectUris |
[]string |
No | [] |
Valid redirect URIs (callback URLs). Use * for local development only. |
webOrigins |
[]string |
No | [] |
Valid web origins for CORS. Use * for local development only. |
postLogoutRedirectUris |
[]string |
No | [] |
Valid post-logout redirect URIs |
Example - Web Application:
spec:
redirectUris:
- "https://app.example.com/callback"
- "https://app.example.com/silent-renew"
webOrigins:
- "https://app.example.com"
postLogoutRedirectUris:
- "https://app.example.com"
Example - Development (⚠️ Do not use in production):
Client Settings¶
Advanced client configuration options.
Basic Settings¶
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
settings.enabled |
boolean | No | true |
Enable/disable the client |
settings.alwaysDisplayInConsole |
boolean | No | false |
Always display in admin console |
settings.clientAuthenticatorType |
string |
No | client-secret |
Client authentication type. Options: client-secret, client-jwt, client-secret-jwt, client-x509 |
OAuth2 Flow Configuration¶
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
settings.standardFlowEnabled |
boolean | No | true |
Enable standard flow (authorization code flow) - recommended for web apps |
settings.implicitFlowEnabled |
boolean | No | false |
Enable implicit flow (deprecated, use PKCE instead) |
settings.directAccessGrantsEnabled |
boolean | No | true |
Enable direct access grants (resource owner password credentials flow) |
settings.serviceAccountsEnabled |
boolean | No | false |
Enable service accounts (client credentials flow) for M2M |
OAuth2 Flow Guide:
- Authorization Code Flow (
standardFlowEnabled: true): Best for traditional web apps with backend - Authorization Code + PKCE (
standardFlowEnabled: true,publicClient: true): Best for SPAs and mobile apps - Client Credentials (
serviceAccountsEnabled: true): Best for machine-to-machine (service accounts) - Resource Owner Password (
directAccessGrantsEnabled: true): Only use when other flows are not possible
Example - Web App:
spec:
publicClient: false
settings:
standardFlowEnabled: true
implicitFlowEnabled: false
directAccessGrantsEnabled: false
serviceAccountsEnabled: false
Example - SPA with PKCE:
spec:
publicClient: true
settings:
standardFlowEnabled: true
implicitFlowEnabled: false
directAccessGrantsEnabled: false
Example - Service Account (M2M):
spec:
publicClient: false
settings:
standardFlowEnabled: false
directAccessGrantsEnabled: false
serviceAccountsEnabled: true
Consent and Token Settings¶
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
settings.consentRequired |
boolean | No | false |
Require user consent |
settings.displayOnConsentScreen |
boolean | No | true |
Display on consent screen |
settings.includeInTokenScope |
boolean | No | true |
Include in token scope |
settings.accessTokenLifespan |
integer | No | - | Access token lifespan in seconds (overrides realm default) |
settings.refreshTokenLifespan |
integer | No | - | Refresh token lifespan in seconds (overrides realm default) |
Example:
spec:
settings:
consentRequired: true # Require user consent
accessTokenLifespan: 600 # 10 minutes
refreshTokenLifespan: 86400 # 24 hours
Authentication Flows¶
Override default authentication flows for this client.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
authenticationFlows.browserFlow |
string |
No | - | Browser authentication flow override |
authenticationFlows.directGrantFlow |
string |
No | - | Direct grant authentication flow override |
authenticationFlows.clientAuthenticationFlow |
string |
No | - | Client authentication flow override |
Example:
Scopes and Mappers¶
Configure client scopes and protocol mappers for claims customization.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
defaultClientScopes |
[]string |
No | [] |
Default client scopes (always included) |
optionalClientScopes |
[]string |
No | [] |
Optional client scopes (user can opt-in) |
protocolMappers |
[]object | No | [] |
Protocol mappers for custom claims |
protocolMappers[].name |
string |
Yes | - | Mapper name |
protocolMappers[].protocol |
string |
No | openid-connect |
Protocol (e.g., openid-connect, saml) |
protocolMappers[].protocolMapper |
string |
Yes | - | Mapper type |
protocolMappers[].config |
map[string]string |
No | {} |
Mapper configuration |
Common Protocol Mappers:
oidc-usermodel-attribute-mapper: Map user attribute to claimoidc-usermodel-property-mapper: Map user property to claimoidc-group-membership-mapper: Map group memberships to claimoidc-audience-mapper: Add audience to tokenoidc-hardcoded-claim-mapper: Add static claim
Example - User Attribute Mapper:
spec:
protocolMappers:
- name: department-mapper
protocol: openid-connect
protocolMapper: oidc-usermodel-attribute-mapper
config:
user.attribute: department
claim.name: department
jsonType.label: String
id.token.claim: "true"
access.token.claim: "true"
userinfo.token.claim: "true"
Example - Audience Mapper:
spec:
protocolMappers:
- name: api-audience
protocol: openid-connect
protocolMapper: oidc-audience-mapper
config:
included.client.audience: api-server
access.token.claim: "true"
Example - Hardcoded Claim:
spec:
protocolMappers:
- name: environment-claim
protocol: openid-connect
protocolMapper: oidc-hardcoded-claim-mapper
config:
claim.name: environment
claim.value: production
access.token.claim: "true"
Roles and Permissions¶
Configure client-specific roles and service account permissions.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
clientRoles |
[]string |
No | [] |
Client-specific roles to create |
serviceAccountRoles.realmRoles |
[]string |
No | [] |
Realm roles to assign to service account |
serviceAccountRoles.clientRoles |
mapstringstring |
No | {} |
Client roles to assign to service account (by client ID) |
Example - Client Roles:
Example - Service Account with Permissions:
spec:
settings:
serviceAccountsEnabled: true
serviceAccountRoles:
realmRoles:
- offline_access
- uma_authorization
clientRoles:
api-server:
- read:data
- write:data
admin-console:
- view-users
Advanced Configuration¶
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
attributes |
map[string]string |
No | {} |
Additional client attributes |
Example:
spec:
attributes:
pkce.code.challenge.method: S256 # Require PKCE with SHA-256
post.logout.redirect.uris: "+" # Allow any registered redirect URI
Secret Management¶
Configure how client credentials are managed.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
regenerateSecret |
boolean | No | false |
Regenerate client secret on update |
secretName |
string |
No | <client-name>-client-secret |
Name of Kubernetes secret for client credentials |
manageSecret |
boolean | No | true |
Create and manage Kubernetes secret for credentials |
secretMetadata.labels |
map[string]string |
No | {} |
Labels to add to the managed secret |
secretMetadata.annotations |
map[string]string |
No | {} |
Annotations to add to the managed secret |
clientSecret.name |
string |
No | - | Name of existing secret containing the client secret |
clientSecret.key |
string |
No | - | Key in secret data containing the value |
Manual Client Secret:
To use an existing secret instead of generating one, specify clientSecret. This is useful for migrations or when secrets are managed externally (e.g., by SealedSecrets or ExternalSecrets). When set, secretRotation must be disabled.
Automatic Features:
- Recreation: If the managed secret is deleted, the operator automatically recreates it.
- Garbage Collection: Secrets have an OwnerReference to the Client CR, ensuring they are deleted when the client is deleted.
Example:
spec:
manageSecret: true
secretName: webapp-credentials
regenerateSecret: false # Only regenerate manually
secretMetadata:
labels:
app: webapp
annotations:
description: "Credentials for webapp"
Example - Manual Secret:
spec:
clientId: legacy-app
manageSecret: true
# Use existing secret "legacy-secret"
clientSecret:
name: legacy-client-secret
key: client-secret
secretRotation:
enabled: false # Must be disabled when using manual secret
The operator creates a secret with the following keys:
- client-id: Client ID
- client-secret: Client secret (confidential clients only)
- issuer: OIDC issuer URL
- token-endpoint: Token endpoint URL
- auth-endpoint: Authorization endpoint URL
Status Fields¶
| Field | Type | Description |
|---|---|---|
phase |
string |
Current phase: Pending, Provisioning, Ready, Failed, Updating, Degraded |
message |
string |
Human-readable status message |
reason |
string |
Reason for current phase |
observedGeneration |
integer | Generation of spec that was last processed |
clientId |
string |
Client ID in Keycloak |
internalId |
string |
Internal Keycloak client ID (UUID) |
realm |
string |
Realm name |
publicClient |
boolean | Whether this is a public client |
keycloakInstance |
string |
Keycloak instance managing this client |
credentialsSecret |
string |
Name of secret containing client credentials |
endpoints.auth |
string |
OIDC authorization endpoint |
endpoints.token |
string |
OIDC token endpoint |
endpoints.userinfo |
string |
OIDC userinfo endpoint |
endpoints.jwks |
string |
OIDC JWKS endpoint |
endpoints.issuer |
string |
OIDC issuer |
endpoints.endSession |
string |
OIDC end session endpoint |
createdRoles |
[]string |
List of created client roles |
appliedMappers |
[]string |
List of applied protocol mappers |
lastHealthCheck |
string (datetime) |
Last health check timestamp |
lastUpdated |
string (datetime) |
Last update timestamp |
Complete Examples¶
Web Application (Confidential)¶
Traditional server-side web application with backend.
# yaml-language-server: $schema=https://vriesdemichael.github.io/keycloak-operator/schemas/v1/KeycloakClient.json
apiVersion: vriesdemichael.github.io/v1
kind: KeycloakClient
metadata:
name: webapp
namespace: production
spec:
clientId: webapp-production
clientName: "Production Web App"
realmRef:
name: production-realm
namespace: production
publicClient: false # Confidential client
redirectUris:
- "https://app.example.com/callback"
- "https://app.example.com/silent-renew"
webOrigins:
- "https://app.example.com"
postLogoutRedirectUris:
- "https://app.example.com"
settings:
standardFlowEnabled: true # Authorization code flow
implicitFlowEnabled: false
directAccessGrantsEnabled: false
serviceAccountsEnabled: false
consentRequired: false
defaultClientScopes:
- profile
- email
- roles
Single Page Application (Public with PKCE)¶
Modern SPA using authorization code flow with PKCE.
# yaml-language-server: $schema=https://vriesdemichael.github.io/keycloak-operator/schemas/v1/KeycloakClient.json
apiVersion: vriesdemichael.github.io/v1
kind: KeycloakClient
metadata:
name: spa
namespace: production
spec:
clientId: spa-production
realmRef:
name: production-realm
namespace: production
publicClient: true # Public client (no secret)
redirectUris:
- "https://app.example.com/callback"
- "https://app.example.com/silent-renew.html"
webOrigins:
- "https://app.example.com"
settings:
standardFlowEnabled: true # Auth code + PKCE
implicitFlowEnabled: false
directAccessGrantsEnabled: false
attributes:
pkce.code.challenge.method: S256 # Require PKCE with SHA-256
Mobile Application (Public with PKCE)¶
Mobile app using custom URI schemes.
# yaml-language-server: $schema=https://vriesdemichael.github.io/keycloak-operator/schemas/v1/KeycloakClient.json
apiVersion: vriesdemichael.github.io/v1
kind: KeycloakClient
metadata:
name: mobile-app
namespace: production
spec:
clientId: mobile-app
realmRef:
name: production-realm
namespace: production
publicClient: true
redirectUris:
- "myapp://callback" # Custom URI scheme
- "com.example.myapp://callback" # Reverse domain notation
settings:
standardFlowEnabled: true # Auth code + PKCE
implicitFlowEnabled: false
directAccessGrantsEnabled: false
attributes:
pkce.code.challenge.method: S256
API / Resource Server (Bearer-Only)¶
Backend API that only validates tokens.
# yaml-language-server: $schema=https://vriesdemichael.github.io/keycloak-operator/schemas/v1/KeycloakClient.json
apiVersion: vriesdemichael.github.io/v1
kind: KeycloakClient
metadata:
name: api-server
namespace: production
spec:
clientId: api-server
realmRef:
name: production-realm
namespace: production
bearerOnly: true # Only validates tokens, doesn't initiate login
publicClient: false
Service Account (Machine-to-Machine)¶
Backend service using client credentials flow.
# yaml-language-server: $schema=https://vriesdemichael.github.io/keycloak-operator/schemas/v1/KeycloakClient.json
apiVersion: vriesdemichael.github.io/v1
kind: KeycloakClient
metadata:
name: backend-service
namespace: production
spec:
clientId: backend-service
realmRef:
name: production-realm
namespace: production
publicClient: false
settings:
standardFlowEnabled: false # No interactive flows
directAccessGrantsEnabled: false
serviceAccountsEnabled: true # Client credentials flow
serviceAccountRoles:
realmRoles:
- offline_access
clientRoles:
api-server:
- read:data
- write:data
Client with Custom Claims¶
Client with custom protocol mappers for additional claims.
# yaml-language-server: $schema=https://vriesdemichael.github.io/keycloak-operator/schemas/v1/KeycloakClient.json
apiVersion: vriesdemichael.github.io/v1
kind: KeycloakClient
metadata:
name: webapp-with-claims
namespace: production
spec:
clientId: webapp
realmRef:
name: production-realm
namespace: production
redirectUris:
- "https://app.example.com/callback"
protocolMappers:
# Map user department to claim
- name: department
protocolMapper: oidc-usermodel-attribute-mapper
config:
user.attribute: department
claim.name: department
jsonType.label: String
id.token.claim: "true"
access.token.claim: "true"
userinfo.token.claim: "true"
# Map groups to claim
- name: groups
protocolMapper: oidc-group-membership-mapper
config:
claim.name: groups
full.path: "false"
id.token.claim: "true"
access.token.claim: "true"
userinfo.token.claim: "true"
# Add static environment claim
- name: environment
protocolMapper: oidc-hardcoded-claim-mapper
config:
claim.name: env
claim.value: production
access.token.claim: "true"
# Add audience
- name: api-audience
protocolMapper: oidc-audience-mapper
config:
included.client.audience: api-server
access.token.claim: "true"
Retrieving Client Credentials¶
For confidential clients, credentials are stored in a Kubernetes secret:
# Get client secret
kubectl get secret webapp-client-secret \
-n production \
-o jsonpath='{.data.client-secret}' | base64 -d
# Get all credentials
kubectl get secret webapp-client-secret \
-n production \
-o json | jq '.data | map_values(@base64d)'
See Also¶
Related CRD References:
- Keycloak CRD Reference - Configure Keycloak instances
- KeycloakRealm CRD Reference - Configure realms
Configuration Guides:
- End-to-End Setup - Complete client configuration with OAuth2 testing
- Quick Start Guide - Create your first client
Examples:
- Client Examples - Production-ready client configurations
- Web Applications - Authorization Code Flow with PKCE
- Single Page Applications - Implicit/Authorization Code Flow
- Service Accounts - Client Credentials Flow
- Mobile Apps - Authorization Code Flow with PKCE
Architecture & Operations:
- Architecture - How client reconciliation works
- Security Model - How clients authenticate
- Troubleshooting: Client Issues - Common client problems