Skip to content

feat: add configurable OIDC authentication for enterprise deployments#442

Open
rrbanda wants to merge 10 commits into
google:mainfrom
rrbanda:feat/oidc-auth
Open

feat: add configurable OIDC authentication for enterprise deployments#442
rrbanda wants to merge 10 commits into
google:mainfrom
rrbanda:feat/oidc-auth

Conversation

@rrbanda
Copy link
Copy Markdown

@rrbanda rrbanda commented May 16, 2026

Summary

Adds optional, provider-agnostic OIDC authentication to ADK Web UI for enterprise deployments where agents are managed by platforms like Kagenti with SPIFFE/SPIRE zero-trust security.

  • Opt-in: Auth disabled by default. Zero impact on existing users.
  • Provider-agnostic: Uses oidc-client-ts (standard OIDC). Works with Keycloak, Okta, Auth0, Azure AD, or any OIDC-compliant provider.
  • Runtime-configurable: Enable auth via runtime-config.json or Kubernetes ConfigMap -- no image rebuild needed.
  • Fail-closed security: When auth is enabled but token is unavailable, requests are blocked rather than sent without credentials.
  • PKCE enforced: Authorization Code flow with Proof Key for Code Exchange.
  • Angular 21 patterns: provideAppInitializer, functional interceptor via provideHttpClient(withInterceptors([...])), functional route guard.

New files

  • src/app/core/auth/ -- AuthService (oidc-client-ts UserManager), functional HTTP interceptor, route guard, config resolver with Window type augmentation
  • src/app/components/user-menu/ -- Authenticated user menu component (name, email, roles from JWT, logout)
  • src/silent-check-sso.html -- Silent token refresh for background renewal
  • deploy/ -- Dockerfile (multi-stage, non-root nginx) and OpenShift manifests with ConfigMap-based auth config
  • 5 Karma/Jasmine spec files following existing initTestBed() pattern

Modified files

  • src/main.ts -- provideAppInitializer for auth init, provideHttpClient with functional interceptor, visible error on bootstrap failure
  • src/app/app-routing.module.ts -- Auth guard on root route
  • src/app/components/chat/ -- Conditionally show auth user menu vs original user ID editor
  • src/app/core/services/agent.service.ts -- Bearer token on SSE fetch, response.ok check before reading stream
  • src/app/core/models/RuntimeConfig.ts -- AuthConfig interface (authority, clientId, scopes)
  • README.md -- Authentication section with setup guide, supported providers, ConfigMap injection, Kagenti/SPIFFE integration

Configuration example

{
  "backendUrl": "http://localhost:8000",
  "auth": {
    "enabled": true,
    "authority": "https://keycloak.example.com/realms/my-realm",
    "clientId": "adk-web-ui"
  }
}
Provider Authority URL
Keycloak https://keycloak.example.com/realms/{realm}
Okta https://dev-{id}.okta.com
Auth0 https://{tenant}.auth0.com
Azure AD https://login.microsoftonline.com/{tenant}/v2.0

Security model

Browser --OIDC--> Identity Provider --JWT--> Browser
   | Authorization: Bearer <JWT>
Envoy AuthBridge (Kagenti sidecar, validates JWT) --> ADK Agent
   | SPIFFE/SPIRE mTLS (automatic via ztunnel)
Backend Services (SonataFlow, MCP servers)

Design decisions

  • oidc-client-ts over keycloak-js: Provider-agnostic, standard OIDC, smaller surface, no Keycloak-specific config (realm in adapter)
  • Functional interceptor over class-based: APP_INITIALIZER is deprecated in Angular 21; provideHttpClient(withInterceptors([fn])) is the modern pattern
  • Fail-closed: Missing token throws error rather than sending unauthenticated request -- prevents ambiguous 401/403 from backend
  • Idempotent init: AuthService.init() deduplicates concurrent calls via stored promise
  • WebSocket auth: Removed query-param token (log/referrer leakage risk); documented as known limitation requiring server-side protocol changes

Test plan

  • Auth disabled (default): UI loads without login, all existing behavior unchanged
  • Auth enabled with real Keycloak (Kagenti realm): OIDC redirect, login, token exchange, user menu with name/email/roles, logout
  • Agent prompts work with auth enabled (session creation, SSE streaming, tool calls, LLM response)
  • Build passes (npx ng build) with zero new errors
  • Unit tests: auth.service, auth.interceptor, auth.guard, auth.config, user-menu component
  • Tested on OpenShift with containerized image (quay.io), ConfigMap auth config, and real Keycloak
  • No secrets or environment-specific values in committed code
  • Apache 2.0 license headers on all new files, copyright year consistent (2025)

rrbanda added 6 commits May 16, 2026 14:22
…ployments

Adds optional OIDC authentication support to ADK Web UI, enabling
enterprise deployments where agents are managed by Kagenti with
SPIFFE/SPIRE zero-trust security.

When auth.enabled is true in runtime-config.json (or injected via
window.__ADK_CONFIG__ ConfigMap), users must authenticate via an
OIDC provider before accessing the UI. When disabled (default),
behavior is unchanged.

Key changes:
- Add AuthConfig to RuntimeConfig model
- Add AuthService (keycloak-js) for OIDC lifecycle management
- Add AuthInterceptor to attach Bearer tokens to API requests
- Add AuthGuard for route protection
- Add runtime config resolution (ConfigMap + runtime-config.json)
- Add AuthUserMenuComponent showing authenticated user info + logout
- Wire up APP_INITIALIZER and HTTP_INTERCEPTORS in main.ts
- Add silent-check-sso.html for background token refresh

Design principles:
- Opt-in: auth disabled by default, zero impact on existing users
- Provider-agnostic: standard OIDC, works with Keycloak/Okta/Auth0
- Runtime-configurable: enable via ConfigMap without rebuilding
- Zero backend changes: auth enforced at UI + interceptor level
…nent

The component template uses <app-markdown> but the class was imported
without being added to the @component imports array, causing build
failures.
The AuthInterceptor only covers Angular HttpClient requests, but
the SSE streaming endpoint (run_sse) uses raw fetch() and WebSocket
connections bypass HttpClient entirely. Without this fix, Kagenti's
Envoy AuthBridge would reject these requests when OIDC auth is
enabled.

- AgentService.runSse: inject AuthService, get token before fetch,
  add Authorization header to SSE requests
- WebSocketService.connect: inject AuthService, append token as
  query parameter (standard WS auth pattern since browser WebSocket
  API doesn't support custom headers)
- Update WebSocketService interface for async connect signature
Critical fixes:
- Replace keycloak-js with oidc-client-ts for provider-agnostic OIDC
  support (works with Keycloak, Okta, Auth0, Azure AD, any OIDC provider)
- Remove WebSocket token-from-query-string (log/referrer leakage risk);
  document as known limitation requiring server-side protocol changes

Major fixes:
- Make AuthService.init() idempotent (deduplicate concurrent calls)
- Fail-closed: block requests when auth enabled but token missing,
  instead of sending unauthenticated requests
- Fix getToken() to propagate refresh failures and trigger re-login
- Add SSE response.ok check before reading stream body
- Improve auth guard: redirect to login instead of blank page
- Handle bootstrap auth failure with visible error state
- Migrate to Angular 21 APIs: provideAppInitializer (replaces
  deprecated APP_INITIALIZER), functional interceptor (replaces
  class-based HTTP_INTERCEPTORS), provideHttpClient

Minor fixes:
- Add Window augmentation for __ADK_CONFIG__ (removes any casts)
- Export UserInfo as type from barrel
- Wire scopes config through to OIDC client
- Add Apache 2.0 license headers to all new files
- Consistent copyright year (2025)

Testing:
- Add Karma/Jasmine specs for auth.service, auth.interceptor,
  auth.guard, auth.config, and user-menu component
- Tests follow existing initTestBed() pattern for 1p compatibility

Documentation:
- Add Authentication section to README covering OIDC setup,
  Kubernetes ConfigMap injection, Kagenti/SPIFFE integration,
  supported providers, and security model
Includes:
- ConfigMap for runtime auth config (OIDC authority, clientId)
- Deployment with nginx serving the SPA + API reverse proxy
- nginx config with SPA fallback, API proxy, security headers
- Service and Route with TLS edge termination
- Dockerfile for multi-stage production build

Auth is configurable via ConfigMap -- update authority/clientId
and restart the pod. No image rebuild needed.
- Use sed to rewrite pid path to /tmp/nginx.pid (writable by non-root)
- Remove 'user nginx' directive for arbitrary UID compatibility
- Extract nginx.conf to separate file for ConfigMap override
- Set non-root USER 101 for OpenShift restricted SCC
@google-cla
Copy link
Copy Markdown

google-cla Bot commented May 16, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

rrbanda added 4 commits May 16, 2026 17:56
- Fix deploy/openshift.yaml: use quay.io/rbrhssa/adk-web:latest image
  instead of nginx:1.27-alpine + emptyDir, add imagePullSecrets,
  add proxy_read_timeout 300s for SSE, add OIDC provider format comments
- Add to README: step-by-step OpenShift deployment with oc commands,
  Keycloak client creation guide, and demo test prompts for both
  F5 provisioning and branch monitoring use cases
OpenShift deployment manifests (Dockerfile, nginx config, K8s YAML)
are infrastructure-specific and should not be in the core repo.
Users deploy according to their own platform. The deploy files
remain available in the rrbanda/adk-web fork for reference.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant