Clients represent OAuth2 applications that authenticate users through Uitsmijter. Each client belongs to a tenant and has its own configuration, redirect URIs, and access credentials.
This guide shows you how to work with clients using kubectl commands.
A client is an OAuth2 application that can:
- Authenticate users via the tenant’s authentication providers
- Request authorization codes and exchange them for access tokens
- Access protected resources on behalf of authenticated users
- Track metrics including active sessions and denied login attempts
Each client has:
- Unique identifier: UUID-based client_id
- Optional secret: For confidential clients (server-side applications)
- Redirect URIs: Allowed callback URLs after authentication
- Grant types: Supported OAuth2 flows (authorization_code, refresh_token, password)
- Scopes: Permissions the client can request
For conceptual information, see Entities. For configuration details, see Tenant and client configuration.
View all clients across all namespaces:
kubectl get clients -A
Example output:
NAMESPACE NAME CLIENT-ID SESSIONS DENIED AGE
cheese cheese-web 9095A4F2-35B2-48B1-A325-309CA324B97E 8 2 5d
cheese cheese-mobile A1B2C3D4-E5F6-7890-ABCD-EF1234567890 4 0 5d
cheese cheese-api B2C3D4E5-F6A7-8901-BCDE-F12345678901 0 0 5d
ham ham-app C3D4E5F6-A7B8-9012-CDEF-123456789012 8 1 5d
uitsmijter admin-client D4E5F6A7-B8C9-0123-DEF1-234567890123 0 0 5d
The output shows:
- NAMESPACE: Kubernetes namespace (matches the tenant’s namespace)
- NAME: Client identifier
- CLIENT-ID: UUID used in OAuth2 flows
- SESSIONS: Number of active user sessions for this client
- DENIED: Number of failed login attempts
- AGE: Time since client was created
List clients in a specific namespace:
kubectl get clients -n cheese
Get detailed information about a specific client:
kubectl describe client cheese-web -n cheese
Example output:
Name: cheese-web
Namespace: cheese
Labels: <none>
Annotations: <none>
API Version: uitsmijter.io/v1
Kind: Client
Metadata:
Creation Timestamp: 2025-11-22T10:35:12Z
Generation: 1
Resource Version: 12456
UID: b1c2d3e4-f5a6-7890-2345-678901bcdefg
Spec:
Client ID: 9095A4F2-35B2-48B1-A325-309CA324B97E
Grant Types:
authorization_code
refresh_token
Redirect URIs:
https://app.example.com/callback
https://app.example.com/oauth/callback
Scopes:
read
write
profile
Secret: <set>
Tenant: cheese
Status:
Active Sessions: 8
Denied Attempts: 2
Last Activity: 2025-11-27T08:45:22Z
Last Denied: 2025-11-26T15:30:10Z
Phase: Active
Events: <none>
The kubectl describe output provides:
- Spec: Complete client configuration
- Client ID: UUID for OAuth2 authorization
- Grant Types: Supported OAuth2 flows
- Redirect URIs: Allowed callback URLs
- Scopes: Available permissions
- Secret: Whether a client secret is configured (value is not shown for security)
- Tenant: Associated tenant name
- Status: Real-time metrics
- Active Sessions: Current logged-in users
- Denied Attempts: Failed login count
- Last Activity: Timestamp of most recent successful authentication
- Last Denied: Timestamp of most recent failed authentication
- Phase: Operational status
Create a new client using a YAML manifest:
# client-example.yaml
apiVersion: "uitsmijter.io/v1"
kind: Client
metadata:
name: webapp-client
namespace: production
spec:
tenant: example-tenant
client_id: F1E2D3C4-B5A6-7890-CDEF-123456789ABC
secret: "your-secure-client-secret-here"
redirect_uris:
- https://webapp.example.com/oauth/callback
- https://webapp.example.com/auth/callback
grant_types:
- authorization_code
- refresh_token
scopes:
- read
- write
- profile
- email
Apply the client configuration:
kubectl apply -f client-example.yaml
Verify the client was created:
kubectl get client webapp-client -n production
Client ID Generation: Generate a secure UUID for the client_id:
uuidgen | tr '[:lower:]' '[:upper:]'
Client Secret (Confidential Clients): For server-side applications, generate a strong secret:
openssl rand -base64 32
Public Clients:
For browser-based or mobile apps, omit the secret field and use PKCE:
spec:
tenant: example-tenant
client_id: G2F3E4D5-C6B7-8901-DEFG-234567890BCD
# No secret for public clients
redirect_uris:
- https://spa.example.com/callback
grant_types:
- authorization_code
- refresh_token
scopes:
- read
- profile
isPkceOnly: true
Edit a client configuration:
kubectl edit client cheese-web -n cheese
Update redirect URIs or scopes and save to apply changes.
Alternatively, update the YAML file and reapply:
kubectl apply -f client-example.yaml
Watch client status in real-time:
kubectl get clients -n cheese --watch
Check active session count for a specific client:
kubectl get client cheese-web -n cheese -o jsonpath='{.status.activeSessions}'
View denied login attempts:
kubectl get client cheese-web -n cheese -o jsonpath='{.status.deniedAttempts}'
Check last authentication activity:
kubectl get client cheese-web -n cheese -o jsonpath='{.status.lastActivity}'
View all clients sorted by active sessions:
kubectl get clients -A -o json | \
jq '.items | sort_by(.status.activeSessions) | reverse |
.[] | {name: .metadata.name, namespace: .metadata.namespace,
sessions: .status.activeSessions}'
Find clients with failed login attempts:
kubectl get clients -A -o json | \
jq '.items[] | select(.status.deniedAttempts > 0) |
{name: .metadata.name, denied: .status.deniedAttempts}'
The denied attempts counter is tracked in memory and persists across pod restarts via Redis. To reset the counter, you can delete and recreate the client status (this does not affect active sessions):
# The counter will reset when new login attempts occur
kubectl annotate client cheese-web -n cheese \
uitsmijter.io/reset-metrics="$(date +%s)" --overwrite
Delete a client:
kubectl delete client cheese-web -n cheese
Warning: Deleting a client invalidates all active sessions and refresh tokens for that client. Users will need to re-authenticate.
Verify deletion:
kubectl get client cheese-web -n cheese
Expected output:
Error from server (NotFound): clients.uitsmijter.io "cheese-web" not found
Clients can be in different phases:
| Phase | Description |
|---|---|
Active |
Client is operational and can authenticate users |
Pending |
Client is being initialized or tenant association is being established |
Failed |
Configuration error (invalid tenant, malformed redirect URIs, etc.) |
Check client phase:
kubectl get client cheese-web -n cheese -o jsonpath='{.status.phase}'
Check if the associated tenant exists:
kubectl get tenant cheese -n cheese
Verify the tenant name matches the client’s spec:
kubectl get client cheese-web -n cheese -o jsonpath='{.spec.tenant}'
Check for configuration errors:
kubectl describe client cheese-web -n cheese
Common issues:
- Invalid tenant reference: Tenant does not exist in the same namespace
- Malformed redirect URIs: URIs must be absolute URLs with https:// scheme (http:// allowed for localhost)
- Missing client_id: Must be a valid UUID
- Duplicate client_id: Another client already uses this UUID
Check if Uitsmijter pods are running:
kubectl get pods -n uitsmijter -l app=uitsmijter
Verify Redis session storage is healthy:
kubectl get pods -n uitsmijter -l app=uitsmijter-sessions
kubectl logs -n uitsmijter -l app=uitsmijter-sessions --tail=50
Check Uitsmijter logs for session-related errors:
kubectl logs -n uitsmijter -l app=uitsmijter --tail=100 | grep -i session
This may indicate:
- Brute force attack: Monitor for patterns in authentication failures
- Misconfigured application: Application sending wrong credentials
- User lockout issue: Users repeatedly entering wrong passwords
Check Uitsmijter logs for denied login details:
kubectl logs -n uitsmijter -l app=uitsmijter --tail=200 | grep -i "login.*fail"
Review Prometheus metrics for detailed failure analysis:
kubectl port-forward -n uitsmijter svc/uitsmijter 8080:8080
curl http://localhost:8080/metrics | grep uitsmijter_login_failure
Uitsmijter supports multiple OAuth2 grant types:
| Grant Type | Use Case | Requires Secret | Supports Refresh |
|---|---|---|---|
authorization_code |
Server-side web apps, SPAs with PKCE | Optional | Yes |
refresh_token |
Renew access tokens without re-login | Same as client | N/A |
password |
Legacy apps only (not recommended) | Yes | No |
Configure grant types in the client spec:
spec:
grant_types:
- authorization_code
- refresh_token
Security Recommendation: Use
authorization_codewith PKCE for all modern applications. Thepasswordgrant type should only be enabled for legacy applications that cannot be updated.
Get client as JSON:
kubectl get client cheese-web -n cheese -o json
Get client as YAML for backup:
kubectl get client cheese-web -n cheese -o yaml > client-backup.yaml
Extract client_id programmatically:
kubectl get client cheese-web -n cheese -o jsonpath='{.spec.client_id}'
List all clients for a specific tenant:
kubectl get clients -A -o json | \
jq '.items[] | select(.spec.tenant=="cheese") |
{name: .metadata.name, client_id: .spec.client_id}'
Find clients without active sessions:
kubectl get clients -A -o json | \
jq '.items[] | select(.status.activeSessions == 0) | .metadata.name'
Compare client configurations:
diff <(kubectl get client cheese-web -n cheese -o yaml) \
<(kubectl get client cheese-mobile -n cheese -o yaml)
Client secrets are stored in Kubernetes as plaintext in the client CRD.
Uitsmijter strictly validates redirect URIs to prevent authorization code interception:
- URIs must match one of the configured
redirect_uris
For public clients (no secret), always use PKCE:
/authorize?response_type=code
&client_id=9095A4F2-35B2-48B1-A325-309CA324B97E
&redirect_uri=https://app.example.com/callback
&code_challenge=3VpzZL3DpqEwubIbIVsrOUbvB19kk4yGP7gGaxU/cyQ=
&code_challenge_method=S256
See PKCE Documentation for details.
- Entities - Conceptual overview of clients
- Tenant and client configuration - Detailed configuration reference
- Managing Tenants - Working with tenant resources
- OAuth Flow - Understanding OAuth2 authorization flows
- Grant Types - Supported OAuth2 grant types
- PKCE - Authorization Code Flow with Proof Key for Code Exchange
- Available Endpoints - OAuth2 API endpoints