Skip to content

refactor: relay-only BFF — remove the in-app identity layer (ADR 0014) - #29

Merged
Gkrumbach07 merged 3 commits into
mainfrom
worktree-relay-only-bff
Aug 8, 2026
Merged

refactor: relay-only BFF — remove the in-app identity layer (ADR 0014)#29
Gkrumbach07 merged 3 commits into
mainfrom
worktree-relay-only-bff

Conversation

@Gkrumbach07

@Gkrumbach07 Gkrumbach07 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the shape A / relay-only decision (ADR 0002 (relay-only auth), on the docs/adrs branch → PR #17): the BFF is a pure token relay in every deployment. Browser authentication — login, cookie sessions, refresh, logout, CSRF — belongs to the auth proxy in front of the BFF (oauth2-proxy standalone, kube-auth-proxy federated), which is how every production deployment already ran. The secure-agent-workspace validated pattern deploys this dashboard behind oauth2-proxy today and never used the built-in custodian.

Net: −2,000 lines. One auth pattern, one trust story: the thing in front of me authenticated this request.

Removed

  • The standalone OIDC custodian (~1,500 lines): oidc_handler.go, auth/session.go (AES-256-GCM cookie codec), session_manager.go (transparent refresh), CSRF middleware, and the /auth/discovery, /auth/token-exchange, /auth/session, /auth/logout endpoints + tests
  • Frontend PKCE flow: oidc.ts, AuthCallbackPage, useSession probe; login page is dev-mode only
  • CORS allowlist + ALLOWED_ORIGINS — same-origin-only behind the proxy; the three divergent origin checks collapse to one (same-origin enforcement on the terminal WS handshake)
  • Mode machinery: OIDC_ISSUER/CLIENT_ID/CLIENT_SECRET/SCOPES/USER_ROLE, SESSION_SECRET, DEPLOYMENT_CONTEXT, TrustProxyHeader. AUTH_DISABLED is the only switch left — this also removes the FE/BE mode-detection mismatch class entirely
  • Dead extension surface: workspaceBinding slot, workspaceBinding/resourceLinks feature flags, SandboxPolicyTab.tsx

Fixed

  • Dev-mode whoami returns the configured ADMIN_ROLE instead of hardcoded role names that never matched it (admin pages were silently inaccessible in dev)
  • 401 outside dev mode reloads the page (guarded, once) so the proxy re-authenticates — the old redirect targeted a /login route that doesn't exist in proxied deployments
  • Logout goes straight to LOGOUT_URL (default /oauth2/sign_out), no double fallback
  • make dev-full: the local gateway allows unauthenticated calls so the auth-disabled BFF works against it; Keycloak still mints real JWTs for exercising the Bearer relay path with curl/CLI

Deployment requirement (documented in README)

Unconditional trust in x-forwarded-access-token is safe only when the proxy is the sole network path to the BFF (sidecar, pod-internal port, proxy-only ingress) — the standard contract for every x-forwarded-* consumer.

Relationship to other PRs

Test plan

  • go build ./... && go vet ./... && go test ./... — all green
  • tsc --noEmit, jest (168/168), eslint, prettier — all green
  • webpack production build — green
  • Live validation: make dev-full → Continue as developer → workspaces/sandboxes/terminal
  • Live validation: oauth2-proxy in front → login via Dex/OpenShift SSO → relay path → workspaces render (verified on the ROSA cluster, image pr-29; terminal WS pending a browser check)

🤖 Generated with Claude Code

The BFF is now a pure token relay in every deployment: it reads the bearer
injected by a fronting auth proxy (x-forwarded-access-token) or an explicit
Authorization: Bearer, forwards it to the gateway, and does nothing else
with it. Browser authentication — login, cookie sessions, refresh, logout,
CSRF — is owned by the auth proxy (oauth2-proxy standalone, kube-auth-proxy
federated), which is how every production deployment already ran.

Removed:
- Standalone OIDC custodian: oidc_handler.go, auth/session.go (AES-256-GCM
  cookie codec), session_manager.go (transparent refresh), CSRF middleware,
  and the /auth/discovery, /auth/token-exchange, /auth/session,
  /auth/logout endpoints (+ all their tests)
- Frontend PKCE flow: oidc.ts, AuthCallbackPage, useSession probe; the
  login page is now dev-mode only
- CORS allowlist machinery and ALLOWED_ORIGINS — the BFF is same-origin
  behind its proxy; the one remaining browser check is same-origin
  enforcement on the terminal WebSocket handshake (one implementation,
  down from three divergent ones)
- Mode detection: OIDC_ISSUER/OIDC_CLIENT_ID/OIDC_CLIENT_SECRET/
  OIDC_SCOPES/OIDC_USER_ROLE/SESSION_SECRET/DEPLOYMENT_CONTEXT env vars
  and the TrustProxyHeader gate; AUTH_DISABLED is the only switch left
- Dead extension surface: workspaceBinding slot, workspaceBinding/
  resourceLinks feature flags, SandboxPolicyTab.tsx

Fixed along the way:
- Dev-mode whoami now returns the configured ADMIN_ROLE instead of
  hardcoded role names that never matched it (admin pages were silently
  inaccessible in dev)
- 401 handling outside dev mode reloads the page (once) so the proxy can
  re-authenticate, instead of redirecting to a /login route that doesn't
  exist in proxied deployments
- logout drops the double fallback and goes straight to LOGOUT_URL
- dev-env.sh: gateway allows unauthenticated calls locally so make
  dev-full works with the auth-disabled BFF; Keycloak still mints real
  JWTs for exercising the Bearer relay path

Net: -2,000 lines. go build/vet/test, tsc, jest (168), eslint, prettier,
webpack prod build all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gkrumbach07 added a commit that referenced this pull request Aug 7, 2026
The BFF never terminates authentication — a fronting auth proxy
(oauth2-proxy standalone, kube-auth-proxy federated) owns login, sessions,
refresh, and CSRF in every authenticated deployment. Implementation in
PR #29 (-2,000 lines).

- ADR 0014 (new): relay-only decision; supersedes ADR 0010. Standalone and
  federated become the same architecture with different proxy operators;
  documents the proxy-is-the-only-path deployment invariant
- ADR 0010: marked Superseded — its requirements were right, oauth2-proxy
  meets them on the other side of a network boundary
- ADR 0001 v3: pattern space collapses to token relay; the only auth
  switch left is AUTH_DISABLED
- ADR 0003 v3: 'the BFF is a token relay' — the dumb-pipe doctrine is true
  again, with the drift history recorded
- ADR 0011 v2: three jobs (custody removed); never-list gains 'no auth
  termination'
- ADR 0005/0008/0012: consistency edits (embedding mechanism = dedicated
  proxy flow; WS upgrade auth via proxy; dead flags/slot removed in #29)

CLAUDE.md and .claude rules updated to relay-only reality.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gkrumbach07 and others added 2 commits August 7, 2026 16:38
Its only callers were the deleted auth endpoints.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Config validated live on the ROSA cluster (Dex IdP, gateway audience =
client_id): ID-token passthrough, WebSocket upgrades, and the
unverified-email flag Dex's OpenShift connector requires.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Gkrumbach07
Gkrumbach07 merged commit 92e1335 into main Aug 8, 2026
6 checks passed
Gkrumbach07 added a commit that referenced this pull request Aug 8, 2026
- CLAUDE.md: remove phantom "oidc/ subdir" from project structure
- CONTRIBUTING.md: lint is golangci-lint not go vet
- CONTRIBUTING.md: ADR 0007 reference → .claude/rules/openshell-api.md
- security.md: remove CORS claim (no CORS middleware exists)
- bff-go.md: remove ALLOWED_ORIGINS (removed in PR #29)
- FEATURE_FLAGS.md: remove stale implementation TODOs (all done)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gmenher added a commit to gmenher/openshell-dashboard that referenced this pull request Aug 9, 2026
1. gRPC error extraction: Replace fragile sanitizeGrpcMessage (regex +
   colon-splitting heuristic) with errors.As + GRPCStatus() interface.
   Extracts the original clean gateway message directly, bypassing
   status.FromError's message replacement in grpc-go v1.82+.

2. Add test coverage: TestWriteGrpcErrorWrapped verifies that short
   messages ('not found', 'denied') and wrapped errors are extracted
   cleanly regardless of wrapper context.

3. About modal: Use isAutoFit with autoFitMinModifier for responsive
   layout without mid-word wrapping.

Note: CORS/ALLOWED_ORIGINS change dropped per PR Gkrumbach07#29 which removed
the in-app identity layer entirely.
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