You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implement KeycloakAuthClient for service-account Kagenti authentication via OAuth2 Client Credentials Grant.
Approach Change (2026-06-26)
Previous approach: Per-user identity delegation via RFC 8693 token exchange (TokenExchangeManager). Each user's OIDC token was exchanged for a Kagenti-scoped token with per-user caching and graceful fallback.
Current approach: Service-account authentication via OAuth2 Client Credentials Grant (KeycloakAuthClient). A single service-account token is obtained from Keycloak, cached with expiry buffer, and used for all Kagenti requests. User identity is propagated via X-Backstage-User header for audit purposes (informational only, not for authentication).
Rationale: The service-account approach is simpler, proven in production, and sufficient for current requirements. Per-user token exchange adds complexity (auth proxy dependency, RFC 8693 exchange flow, per-user cache management) without proportional benefit at this stage. Service-account auth with user context headers provides adequate audit trails.
Tasks
From openspec/changes/security-safety-governance/tasks.md section 7:
All tasks complete.KeycloakAuthClient was deduplicated into boost-node/src/KeycloakAuthClient.ts and is shared by both entity providers (kagenti-entity-provider) and the user-facing provider module (boost-backend-module-kagenti).
Config Fields
The config fields are defined in plugins/boost-backend/src/config/schemas.ts. The KeycloakAuthClient consumer reads them at startup:
Key
Default
Description
boost.kagenti.auth.tokenEndpoint
—
Keycloak token endpoint URL
boost.kagenti.auth.clientId
—
OAuth2 client ID for service-account
boost.kagenti.auth.clientSecret
—
OAuth2 client secret (visibility: secret)
boost.kagenti.auth.tokenExpiryBufferSeconds
60
Seconds before expiry to refresh token
Implementation Constraints
These constraints are codified in the specs and must be followed:
Retry limit: On HTTP 401 from Kagenti, invalidate the cached token, fetch a fresh one, and retry the request at most once. If the retried request also returns 401, propagate the error to the caller. Do not loop. (See access-control/spec.md and design.md Decision 4.)
Consumer-applied default for tokenExpiryBufferSeconds: The Zod schema does NOT include .default(60) — raw config resolution bypasses Zod defaults. KeycloakAuthClient applies its own fallback: const buffer = configValue ?? 60.
Partial config detection: The three core auth fields (tokenEndpoint, clientId, clientSecret) are individually optional in the schema (because Keycloak auth itself is optional). When only a subset of the three fields is present, a warning is logged and auth is disabled. When all three are present, KeycloakAuthClient is constructed. (Implemented in readKagentiAuthConfig in module.ts.)
No references to Augment or Citi in code, config keys, comments, or error messages. Boost is a clean-room reimplementation. The only Augment references allowed are in high-level specification context (e.g., "Augment lesson" in boost-context.md).
Credential delivery method: Credentials must be sent as client_id and client_secret in the POST form body (application/x-www-form-urlencoded), not via HTTP Basic auth. This matches the Keycloak client configuration (client_secret_post). See access-control/spec.md.
Reference Implementation
The augment workspace has a working KeycloakTokenManager that can be used as a reference for the remaining patterns (401 retry, concurrent deduplication, getTokenForStreaming):
Labels:
ready-to-codeDepends on: Issue 11
Implement
KeycloakAuthClientfor service-account Kagenti authentication via OAuth2 Client Credentials Grant.Approach Change (2026-06-26)
Previous approach: Per-user identity delegation via RFC 8693 token exchange (
TokenExchangeManager). Each user's OIDC token was exchanged for a Kagenti-scoped token with per-user caching and graceful fallback.Current approach: Service-account authentication via OAuth2 Client Credentials Grant (
KeycloakAuthClient). A single service-account token is obtained from Keycloak, cached with expiry buffer, and used for all Kagenti requests. User identity is propagated viaX-Backstage-Userheader for audit purposes (informational only, not for authentication).Rationale: The service-account approach is simpler, proven in production, and sufficient for current requirements. Per-user token exchange adds complexity (auth proxy dependency, RFC 8693 exchange flow, per-user cache management) without proportional benefit at this stage. Service-account auth with user context headers provides adequate audit trails.
Tasks
From
openspec/changes/security-safety-governance/tasks.mdsection 7:7.1 Create✅ PR feat(#3647): auth pivot, dev scaffold, entity provider fixes #3648KeycloakAuthClientimplementing OAuth2 Client Credentials Grant7.2 Add token caching with configurable expiry buffer (✅ PR feat(#3647): auth pivot, dev scaffold, entity provider fixes #3648tokenExpiryBufferSeconds, default: 60)7.3 Add max-1-retry on 401 (refresh token and retry once)✅ PR feat(#3309): add Kagenti auth, 401 retry, and user header #36617.4 Add config schema:✅ PR feat(#3647): auth pivot, dev scaffold, entity provider fixes #3648boost.kagenti.auth.{tokenEndpoint, clientId, clientSecret, tokenExpiryBufferSeconds}7.5a Integrate into entity providers — inject bearer token✅ PR feat(#3647): auth pivot, dev scaffold, entity provider fixes #36487.5b Integrate into✅ PR feat(#3309): add Kagenti auth, 401 retry, and user header #3661KagentiApiClient— inject bearer token7.6 Propagate user identity via✅ PR feat(#3309): add Kagenti auth, 401 retry, and user header #3661X-Backstage-Userheader for auditAll tasks complete.
KeycloakAuthClientwas deduplicated intoboost-node/src/KeycloakAuthClient.tsand is shared by both entity providers (kagenti-entity-provider) and the user-facing provider module (boost-backend-module-kagenti).Config Fields
The config fields are defined in
plugins/boost-backend/src/config/schemas.ts. TheKeycloakAuthClientconsumer reads them at startup:boost.kagenti.auth.tokenEndpointboost.kagenti.auth.clientIdboost.kagenti.auth.clientSecretboost.kagenti.auth.tokenExpiryBufferSeconds60Implementation Constraints
These constraints are codified in the specs and must be followed:
Retry limit: On HTTP 401 from Kagenti, invalidate the cached token, fetch a fresh one, and retry the request at most once. If the retried request also returns 401, propagate the error to the caller. Do not loop. (See
access-control/spec.mdanddesign.mdDecision 4.)Consumer-applied default for
tokenExpiryBufferSeconds: The Zod schema does NOT include.default(60)— raw config resolution bypasses Zod defaults.KeycloakAuthClientapplies its own fallback:const buffer = configValue ?? 60.Partial config detection: The three core auth fields (
tokenEndpoint,clientId,clientSecret) are individually optional in the schema (because Keycloak auth itself is optional). When only a subset of the three fields is present, a warning is logged and auth is disabled. When all three are present,KeycloakAuthClientis constructed. (Implemented inreadKagentiAuthConfiginmodule.ts.)No references to Augment or Citi in code, config keys, comments, or error messages. Boost is a clean-room reimplementation. The only Augment references allowed are in high-level specification context (e.g., "Augment lesson" in
boost-context.md).Credential delivery method: Credentials must be sent as
client_idandclient_secretin the POST form body (application/x-www-form-urlencoded), not via HTTP Basic auth. This matches the Keycloak client configuration (client_secret_post). Seeaccess-control/spec.md.Reference Implementation
The augment workspace has a working
KeycloakTokenManagerthat can be used as a reference for the remaining patterns (401 retry, concurrent deduplication,getTokenForStreaming):workspaces/augment/plugins/augment-backend/src/providers/kagenti/client/KeycloakTokenManager.tsworkspaces/augment/plugins/augment-backend/src/providers/kagenti/client/KagentiApiClient.tsworkspaces/augment/plugins/augment-backend/src/providers/kagenti/client/requestCore.tsImportant: Do not copy code from augment. Use it to understand the patterns, then implement cleanly in boost's architecture.
Where the Code Lives
Completed (PR #3648, PR #3661):
plugins/boost-node/src/KeycloakAuthClient.ts—KeycloakAuthClientclass (deduplicated fromkagenti-entity-providerintoboost-nodefor shared use)plugins/boost-node/src/KeycloakAuthClient.test.ts— unit testsplugins/boost-backend-module-kagenti/src/provider/KagentiApiClient.ts— 401 retry, bearer token injection,X-Backstage-Userheaderplugins/boost-backend-module-kagenti/src/provider/KagentiProvider.ts—ChatOptionsthreading for user identity propagationplugins/boost-backend-module-kagenti/src/module.ts— config reading, partial config detection, auth client constructionSpecifications
openspec/changes/security-safety-governance/specs/access-control/spec.md— Service-account auth scenarios (6 scenarios covering token acquisition, streaming, 401 retry, user identity propagation, config, LlamaStack unaffected)openspec/changes/security-safety-governance/design.md— Decision 4 (service-account auth with token caching and streaming support)openspec/changes/security-safety-governance/tasks.md— Section 7 (all tasks)openspec/changes/platform-operations-deployment/specs/runtime-config/spec.md— Config field scenariosspecifications/boost-context.md— Design principles (read Principle 10 for auth context)