fix(cli): mint a JWT for --host remote targets when using an API key
Fixes #6315
Problem
--api-key (and SUPERSET_API_KEY) is documented as a global option, but it is ignored on any command that targets a remote host via --host <machineId>. Those commands exit 1 with:
Error: Session expired
Hint: Run: superset auth login
The message is doubly misleading: the API key is not expired (the same key succeeds against the local host and against hosts list in the same shell), and no session was ever established.
Root cause
The relay authenticates host-service traffic only by verifying a JWKS-signed JWT. The --host <remote> transport (resolveHostTarget.ts) sent the raw bearer — an sk_live_… API key from --api-key / SUPERSET_API_KEY — straight into the Authorization: Bearer header. An API key is not a JWT, so the relay returned UNAUTHORIZED, and the CLI's generic handler rendered any UNAUTHORIZED as "Session expired".
Every other relay client already performs an API-key → JWT exchange (JwtAuthProvider.getJwt, packages/sdk/src/client.ts). The CLI's remote-host path was the missing one.
Fix
- New
packages/cli/src/lib/host-jwt.ts—getHostJwt(bearer), mirroring the exchangeJwtAuthProviderand the SDK already perform:- JWT-shaped tokens (CLI OAuth access tokens are JWKS-signed) pass straight through, no exchange needed;
sk_live_/sk_test_API keys are exchanged for a JWT viaGET {api}/api/auth/tokenwith the key in thex-api-keyheader (better-auth's apiKey plugin reads that header, notAuthorization), cached in-process ~55 min (5 min refresh buffer), keyed by credential.
resolveHostTarget.ts— the remote branch now mints the JWT inside an asyncheaders()hook (tRPC'shttpBatchLinksupports an asyncheadersfunction). This confines the change to the remote branch; all ~16 call sites (workspaces/*,projects/*,terminals/*,agents/*,automations/*, …) are untouched.
No relay or host-service change is needed — the minted JWT carries the same organizationIds/sub claims the relay's checkHostAccess already verifies.
Tests
packages/cli/src/lib/host-jwt.test.ts (6 cases, bun:test, mock fetch + getApiUrl):
- OAuth JWT passes through with no exchange;
sk_live_/sk_test_keys are exchanged via/api/auth/tokenwithx-api-key;- minted JWT is cached per credential and reused (single fetch);
- different credentials do not share a cached JWT;
- exchange failure throws.
Manual verification
The repro pair from the issue (superset workspaces list --host "$REMOTE" --api-key "$KEY") now authenticates; hosts list already worked via the x-api-key path and is unchanged.
Summary by cubic
Fixes remote --host CLI commands using --api-key by minting a JWT before calling the relay, so API key auth works instead of failing with "Session expired" (fixes #6315).
- Bug Fixes
- Added
packages/cli/src/lib/host-jwt.ts:getHostJwtexchangessk_live_/sk_test_keys for a JWT viaGET {api}/api/auth/tokenwithx-api-key, caches 55 min per credential, validates a trimmed non-empty string before caching, passes JWT-like tokens through, and uses a 10sAbortSignal.timeout. - Updated
resolveHostTarget.tsto mint the JWT in an asyncheaders()hook forhttpBatchLink, leaving call sites unchanged. - Expanded
host-jwt.test.tsto cover invalid/missing/whitespace-only tokens (not cached), per-key cache isolation, a real timeout signal, and the 55‑min cache window boundary (re-mints just past the window).
- Added
Summary by CodeRabbit
-
New Features
- Remote host connections now support API-key authentication through automatic token exchange.
- Existing JWT credentials continue to pass through directly.
- Authentication tokens are securely reused for a limited time to reduce repeated requests.
-
Bug Fixes
- Improved authentication handling for remote host relay requests.
- Authentication failures now report relevant HTTP status information.
- Invalid authentication responses are rejected.
- Token exchanges now time out to prevent stalled requests.