Skip to content

fix(proxy): make wrapper auth actually work with current openclaw Control UI#223

Open
cap10bad wants to merge 5 commits into
vignesh07:mainfrom
freshdrops:fix/auth-header-collision
Open

fix(proxy): make wrapper auth actually work with current openclaw Control UI#223
cap10bad wants to merge 5 commits into
vignesh07:mainfrom
freshdrops:fix/auth-header-collision

Conversation

@cap10bad

@cap10bad cap10bad commented Apr 24, 2026

Copy link
Copy Markdown

Summary

Four related auth fixes that are needed together for the wrapper to work correctly with the current Control UI (openclaw >= v2026.4.24). Originally opened for the first bullet only; extended after verifying end-to-end on a live deployment surfaced the rest.

1. Authorization: Bearer always overwrites Basic before proxying

attachGatewayAuthHeader had a !req?.headers?.authorization guard. Once the browser cached Basic creds for the dashboard, every subsequent proxied request leaked Basic <SETUP_PASSWORD> to the gateway and was rejected as a token mismatch. Drop the guard — when OPENCLAW_GATEWAY_TOKEN is set, always rewrite Authorization to Bearer <token> before the proxy call. PR #164 addressed the WS path; this covers HTTP.

2. Bootstrap the gateway token into Control UI localStorage

openclaw v2026.4.24 changed the Control UI auth protocol: the gateway token is now read from localStorage["openclaw.control.settings.v1"].token and sent inside the WS handshake message payload, not the HTTP Authorization header. The wrapper's proxy-level header injection is invisible to the new auth path — connect fails with reason=token_missing and the gateway prints "paste the token in Control UI settings".

On the first GET / after dashboard auth, serve a tiny bootstrap HTML that writes GATEWAY_TOKEN into localStorage, sets a 1-year oc_wrapper_bootstrapped cookie, and reloads. Subsequent loads go straight to the Control UI with auth already resolved client-side. Token never leaves the authenticated dashboard trust boundary.

3. Accept Authorization: Bearer <GATEWAY_TOKEN> on the wrapper itself

requireDashboardAuth previously only accepted Basic, so API/CLI clients already holding the gateway token were forced through the browser Basic prompt. Now accept Bearer (timing-safe compare) and advertise both schemes in WWW-Authenticate. Bearer passes through to the gateway unchanged.

4. Replace native Basic popup with a styled HTML login + session cookie

The browser's native Basic auth dialog is jarring for a production dashboard. On GET with Accept: text/html and no session, return an HTML login page. POST /__login validates SETUP_PASSWORD and sets an HMAC(SETUP_PASSWORD) session cookie (30-day, HttpOnly, Secure, SameSite=Lax) — stateless, invalidates automatically on password change. Basic/Bearer still accepted for curl/CLI clients; successful Basic auth also sets the cookie.

Test plan

  • Existing test suite (12 tests including websocket-upgrade-auth) passes
  • Deployed live to a Railway instance — dashboard auth → Control UI → gateway WS all work without manual token paste
  • curl -H "Accept: text/html" / returns login page (no WWW-Authenticate: Basic challenge)
  • curl / (no Accept) returns WWW-Authenticate: Basic ..., Bearer ... (unchanged for scripts)
  • curl -H "Authorization: Bearer <GATEWAY_TOKEN>" / succeeds
  • After login, session cookie persists for 30 days; clearing site data shows login page again

Nick added 5 commits April 23, 2026 12:13
The browser's Basic <SETUP_PASSWORD> header survived dashboard auth and
leaked to the gateway, causing token mismatch rejections. Drop the
'only if unset' guard so dashboard Basic auth and gateway Bearer auth
no longer fight.
openclaw v2026.4.24 switched the Control UI to read the gateway token
from localStorage['openclaw.control.settings.v1'].token and send it
inside the WS handshake message payload — bypassing the HTTP
Authorization header the wrapper was injecting.

Result: after dashboard auth, the Control UI WS connect is rejected
with reason=token_missing and the gateway hints 'paste the token in
Control UI settings'.

Fix: on the first GET / after dashboard auth, serve a tiny bootstrap
page that writes the known GATEWAY_TOKEN into localStorage and sets a
one-time cookie, then reloads. Subsequent loads go straight to the
Control UI with auth already resolved.

Token only leaves the authenticated dashboard trust boundary (gated by
requireDashboardAuth), same surface as the user pasting it manually.
API clients / CLIs already holding the gateway token were being forced
through the browser Basic prompt because requireDashboardAuth only
accepted Basic. Now accept 'Authorization: Bearer <GATEWAY_TOKEN>' as
an equivalent credential (timing-safe compare) and advertise both
schemes in WWW-Authenticate.
The native browser Basic auth dialog is jarring for a production
dashboard. Replace it with a styled HTML login page that exchanges
SETUP_PASSWORD for a signed session cookie (HMAC of SETUP_PASSWORD,
stateless, invalidates on password change).

- GET /  (HTML)  → login page when no valid session
- POST /__login → sets oc_dashboard_session cookie on success
- Basic/Bearer still accepted for curl/CLI clients; successful Basic
  login also sets the cookie for subsequent browser requests
@cap10bad cap10bad changed the title fix(proxy): always overwrite Authorization with Bearer gateway token fix(proxy): make wrapper auth actually work with current openclaw Control UI Apr 24, 2026
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