Skip to content

feat(api): WS attach accepts OIDC JWTs, not just API keys (closes #807) - #809

Merged
DorianZheng merged 1 commit into
mainfrom
fix/ws-proxy-jwt-auth
Jun 16, 2026
Merged

DorianZheng merged 1 commit into
mainfrom
fix/ws-proxy-jwt-auth

Conversation

@DorianZheng

@DorianZheng DorianZheng commented Jun 16, 2026 •

Copy link
Copy Markdown
Member

What

The API's WS /attach proxy authenticated upgrades only as an API key, so a
browser/OIDC login (Auth0 JWT) — which works for REST — was rejected 401 on the
attach upgrade and cloud exec output never streamed. Closes #807. (PR #804
already made the CLI surface this loudly instead of a silent exit 0.)

Change

BoxliteWsProxyService.authenticate now falls through to JWT when the bearer is
not an API key:

  • verify via the existing JwtStrategy.verifyToken (same JWKS/issuer/audience as
    the HTTP path — no hand-rolled verification),
  • map sub/uid like JwtStrategy.validate,
  • take the org from the URL {prefix} tenant (ATTACH_PATH now captures it),
  • require org membership — identical to the API-key path.

Mirrors current behavior exactly (per scope discussion): membership-only — no
permission gate (there is no box:exec permission and the HTTP exec/attach routes
enforce none), no rate-limiting, and a JWT with no/default tenant is rejected
(org ambiguous for a multi-org user). JwtStrategy is nullable under
skipConnections; authenticate never throws, so the fire-and-forget upgrade
caller can't leak a hung socket.

Verification

  • boxlite-ws-proxy.service.spec.ts: API-key regression + JWT
    member / non-member / cross-org / no-tenant / invalid-token / skipConnections /
    uid-claim, plus matchAttachPath tenant capture. Verified RED→GREEN (the JWT
    acceptance + membership-check tests fail when the JWT path is neutered).
  • Full nx test api green (140 tests).

⚠️ Security review

Cross-org access boundary. The membership check
organizationUserService.findOne(urlTenant, userId) is what stops a JWT user
attaching to boxes in an org they don't belong to — please scrutinize that and
the URL-tenant extraction.

Summary by CodeRabbit

  • Bug Fixes

    • Improved WebSocket /attach upgrade authentication by adding tenant-aware routing and validating both API keys and JWTs with correct precedence.
    • Clarified legacy vs tenant-prefixed attach paths and ensured unauthorized connections are rejected cleanly.
  • Tests

    • Expanded WebSocket authentication tests to cover tenant extraction, matching/non-matching attach paths, API-key and JWT authorization rules, claim mapping, and failure cases (including missing/invalid tokens and missing tenant context).

@DorianZheng
DorianZheng requested a review from a team June 16, 2026 13:42
@coderabbitai

coderabbitai Bot commented Jun 16, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: be262e97-2f1c-497e-b9df-9cd5c9032aff

📥 Commits

Reviewing files that changed from the base of the PR and between b7059ac and 259eeba.

📒 Files selected for processing (3)
  • apps/api/src/boxlite-rest/boxlite-rest-routing.spec.ts
  • apps/api/src/boxlite-rest/boxlite-ws-proxy.service.spec.ts
  • apps/api/src/boxlite-rest/boxlite-ws-proxy.service.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/src/boxlite-rest/boxlite-ws-proxy.service.ts

📝 Walkthrough

Walkthrough

BoxliteWsProxyService gains tenant-aware authentication for WebSocket /attach upgrades. The ATTACH_PATH regex now captures an optional tenant segment; matchAttachPath returns it alongside boxId. JwtStrategy is injected as an optional dependency, and authenticate is rewritten to fall through from API-key auth to JWT verification with OrganizationUser membership enforcement. Routing and proxy service specs are updated accordingly.

Changes

Tenant-aware WS attach authentication

Layer / File(s) Summary
Tenant extraction from ATTACH_PATH and matchAttachPath
apps/api/src/boxlite-rest/boxlite-ws-proxy.service.ts, apps/api/src/boxlite-rest/boxlite-rest-routing.spec.ts
ATTACH_PATH regex updated to capture an optional tenant segment; matchAttachPath returns { boxId, tenant? }; routing spec assertions updated to verify the tenant field for both canonical and legacy default-prefix paths.
JwtStrategy injection and authenticate rewrite
apps/api/src/boxlite-rest/boxlite-ws-proxy.service.ts
JwtStrategy imported and injected as an optional constructor dependency; upgrade passes match.tenant to authenticate; authenticate rewritten to try API-key auth (non-expiry + OrganizationUser membership) then fall through to JWT verification with URL-tenant disambiguation, resolving null on any failure instead of throwing.
Test scaffolding and authenticate/matchAttachPath coverage
apps/api/src/boxlite-rest/boxlite-ws-proxy.service.spec.ts
Adds ServiceInternals type, makeService factory, bearer request helpers, and new suites covering matchAttachPath tenant extraction, API-key precedence, JWT auth with tenant scoping, cross-org rejection, missing/default tenant rejection, invalid JWT, missing bearer header, absent verifier (skipConnections), and OKTA uid/cid claim mapping.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 Hops into the socket stream,
A JWT now joins the team!
Tenant captured in the URL,
API-key or Bearer — both work well.
Membership checked, no cross-org sneak,
The rabbit guards each auth technique! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the primary change: adding OIDC JWT authentication support to the WebSocket attach endpoint, which directly addresses issue #807.
Linked Issues check ✅ Passed The PR implementation fulfills all coding requirements from issue #807: reuses JwtStrategy.verifyToken, maps sub claim to user, extracts organization from URL tenant via ATTACH_PATH regex, enforces membership checking, rejects no-tenant requests, and injects JwtStrategy into boxlite-rest module.
Out of Scope Changes check ✅ Passed All changes are directly scoped to JWT authentication support for the WebSocket attach endpoint: updates to the routing test, proxy service implementation, and comprehensive test coverage align with the stated objectives and issue requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ws-proxy-jwt-auth

Comment @coderabbitai help to get the list of available commands and usage tips.

@DorianZheng DorianZheng added enhancement New feature or request javascript Pull requests that update javascript code labels Jun 16, 2026
…PI keys

The WebSocket `/attach` proxy (`BoxliteWsProxyService`) authenticated upgrades
only as an API key, so a browser/OIDC login (JWT) — which works for REST — was
rejected 401 on the attach upgrade and cloud `exec` output never streamed.
Closes #807.

`authenticate` now falls through to JWT when the bearer is not an API key: verify
via the existing `JwtStrategy.verifyToken` (same JWKS/issuer/audience as the HTTP
path; no hand-rolled verification), map `sub`/`uid` like `JwtStrategy.validate`,
take the organization from the URL `{prefix}` tenant (`ATTACH_PATH` now captures
it), and require org membership — identical to the API-key path and to the
membership-only HTTP exec/attach routes.

Mirrors current behavior exactly: membership-only (no permission gate — there is
no box:exec permission and the HTTP path enforces none), no rate-limiting, and a
JWT with no/`default` tenant is rejected (org ambiguous for a multi-org user).
`JwtStrategy` is nullable under `skipConnections`; `authenticate` never throws so
the fire-and-forget upgrade caller can't leak a hung socket.

Security boundary (cross-org box access) — please review.
@DorianZheng
DorianZheng force-pushed the fix/ws-proxy-jwt-auth branch from b7059ac to 259eeba Compare June 16, 2026 14:01

@G4614 G4614 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can

@DorianZheng
DorianZheng merged commit 75e78ac into main Jun 16, 2026
30 of 31 checks passed
@DorianZheng
DorianZheng deleted the fix/ws-proxy-jwt-auth branch June 16, 2026 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cloud exec output silent for OIDC/browser logins: WS attach proxy rejects JWTs (API-key only)

2 participants