Skip to content

fix(api): jwtProcedure accepts x-api-key sessions, not just Bearer#3895

Merged
saddlepaddle merged 1 commit into
mainfrom
cli-jwt-apikey-fix
Apr 30, 2026
Merged

fix(api): jwtProcedure accepts x-api-key sessions, not just Bearer#3895
saddlepaddle merged 1 commit into
mainfrom
cli-jwt-apikey-fix

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Apr 30, 2026

Summary

jwtProcedure was rejecting any request without an Authorization: Bearer … header before reaching its session fallback. Better-auth's apiKey plugin (enableSessionForAPIKeys: true) populates ctx.session for x-api-key requests, so they're session-equivalent — but they couldn't reach that branch because the upfront throw fired first.

Concretely, the CLI's `--api-key sk_live_…` flag round-tripped through better-auth (returns 200 against `protectedProcedure` like `user.me`), but every `jwtProcedure`-gated procedure (`host.list`, `v2-workspace.list`, etc.) returned 401. `superset hosts list` and `superset workspaces list` were unusable with API keys.

This PR rewrites the procedure to accept any of:

  1. A verifiable Bearer JWT (CLI OAuth flow)
  2. A live `ctx.session` (web cookie OR x-api-key-derived session)

Bot-fix from #3889 preserved: TRPCError instances from `verifyJWT` (revoked/forged tokens) still re-throw rather than silently falling through.

Test plan

  • `bun run typecheck` clean
  • `bun run lint` clean
  • CLI with `SUPERSET_API_KEY=sk_live_…` succeeds on `superset hosts list`, `superset workspaces list`
  • CLI with OAuth JWT still works (no regression)
  • Desktop session-cookie path still works (no regression)
  • Revoked JWT still surfaces UNAUTHORIZED instead of silently using session

Summary by cubic

Allow jwtProcedure to authenticate with a Bearer JWT or any live session (cookie or x-api-key), fixing CLI API key flows that were returning 401. CLI commands like superset hosts list and superset workspaces list now work with --api-key or SUPERSET_API_KEY.

  • Bug Fixes
    • Removed the Bearer-only guard; verify JWT if present, otherwise use ctx.session (includes x-api-key sessions from better-auth).
    • Preserved re-throw of TRPCError from verifyJWT for explicit JWT rejection.
    • Updated unauthorized message to list accepted auth methods.

Written for commit e19be15. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved authentication flow to support multiple credential types: bearer JWT, x-api-key, or session authentication
    • Enhanced error messages to reflect all available authentication methods
    • Fixed authentication handling to allow graceful fallback between credential types

Better-auth's apiKey plugin (`enableSessionForAPIKeys: true`) populates
ctx.session for x-api-key requests, but jwtProcedure was throwing on
the very first line if the request didn't carry an Authorization
Bearer header — so any procedure marked jwtProcedure rejected api-key
auth before reaching the session fallback. The CLI's `superset hosts`,
`workspaces.list`, etc. all 401'd with `--api-key sk_live_…`.

Accept any of: a verifiable Bearer JWT, a successful Bearer JWT
fallback, or an x-api-key-derived session. Throw only if none of
those produce identity. The TRPCError re-throw on explicit JWT
rejection is preserved.

Trailing error message updated to reflect the broader contract.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

📝 Walkthrough

Walkthrough

The jwtProcedure now extracts bearer tokens from the Authorization header and only attempts JWT verification when a token is present, allowing fallback to session authentication without requiring a bearer prefix. Error messages are updated to reflect multiple authentication methods.

Changes

Cohort / File(s) Summary
Authentication Logic
packages/trpc/src/trpc.ts
Modified jwtProcedure to conditionally verify JWT only when a bearer token is extracted from the Authorization header, enabling seamless fallback to session-based authentication without requiring the bearer prefix. Error handling now distinguishes between TRPCError rejections and other failures, with updated error messaging to reflect available authentication options.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit hops through tokens bright,
Bearer tokens handled just right,
Sessions fallback, safe and sound,
Auth flows smooth on hallowed ground,
No more crashes, errors clear—
Better logins, hip hip cheer! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: jwtProcedure now accepts x-api-key sessions in addition to Bearer tokens, which directly addresses the core bug fix in the PR.
Description check ✅ Passed The PR description is comprehensive and well-structured, including problem statement, solution details, testing plan, and implementation notes. All key sections are present and substantively filled out.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 cli-jwt-apikey-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 30, 2026

Greptile Summary

This PR fixes jwtProcedure so that requests authenticated via x-api-key (where better-auth's enableSessionForAPIKeys populates ctx.session) can reach the session fallback branch. The old code threw UNAUTHORIZED immediately if no Authorization: Bearer … header was present, blocking the entire session path for API-key callers.

Confidence Score: 5/5

Safe to merge — the fix is minimal, targeted, and the security invariants (TRPCError re-throw for revoked/forged tokens) are preserved.

Only one file changed with a simple structural refactor. The JWT verification path and its error-handling guard are unchanged; the only behavioral delta is removing the upfront throw that blocked the session branch for non-Bearer callers. No new attack surface is introduced.

No files require special attention.

Important Files Changed

Filename Overview
packages/trpc/src/trpc.ts Refactors jwtProcedure to extract the Bearer token conditionally rather than gating the entire middleware on its presence, enabling x-api-key session fallback; logic is correct and the TRPCError re-throw guard is preserved.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Incoming Request] --> B{Authorization: Bearer header?}
    B -- Yes --> C[Extract bearer token]
    B -- No --> F
    C --> D{verifyJWT succeeds\nwith payload.sub?}
    D -- Yes --> E[✅ Authenticated via JWT\nReturn next with JWT ctx]
    D -- No: TRPCError --> G[❌ Re-throw TRPCError\nrevoked / forged token]
    D -- No: other error or\nno payload.sub --> F{ctx.session present?\ncookie OR x-api-key}
    F -- Yes --> H[✅ Authenticated via Session\nLookup org memberships from DB]
    F -- No --> I[❌ UNAUTHORIZED\nNot authenticated]
Loading

Reviews (1): Last reviewed commit: "fix(api): jwtProcedure accepts x-api-key..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/trpc/src/trpc.ts`:
- Line 75: The current extraction of the token into the variable bearer uses a
case-sensitive startsWith check on authHeader and thus rejects valid schemes
like "bearer ". Update the logic in the area that sets bearer (the authHeader
handling) to perform a case-insensitive check for the "bearer " scheme—e.g.,
compare authHeader.toLowerCase().startsWith("bearer ") or use a case-insensitive
regex—and then slice the original authHeader after the scheme length to extract
the token so casing is preserved in the token value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7da1e768-26bb-4498-a76a-f8d0d48839f5

📥 Commits

Reviewing files that changed from the base of the PR and between b6a890d and e19be15.

📒 Files selected for processing (1)
  • packages/trpc/src/trpc.ts

Comment thread packages/trpc/src/trpc.ts
message: "Bearer token required",
});
}
const bearer = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 30, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Parse bearer scheme case-insensitively to avoid rejecting valid headers.

Authorization auth-scheme tokens are case-insensitive. The current check only accepts exact "Bearer " casing, so valid variants (for example bearer <token>) won’t be parsed.

Suggested patch
-	const bearer = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
+	const bearerMatch = authHeader?.match(/^Bearer\s+(.+)$/i);
+	const bearer = bearerMatch?.[1]?.trim() ?? null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const bearer = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
const bearerMatch = authHeader?.match(/^Bearer\s+(.+)$/i);
const bearer = bearerMatch?.[1]?.trim() ?? null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/trpc/src/trpc.ts` at line 75, The current extraction of the token
into the variable bearer uses a case-sensitive startsWith check on authHeader
and thus rejects valid schemes like "bearer ". Update the logic in the area that
sets bearer (the authHeader handling) to perform a case-insensitive check for
the "bearer " scheme—e.g., compare authHeader.toLowerCase().startsWith("bearer
") or use a case-insensitive regex—and then slice the original authHeader after
the scheme length to extract the token so casing is preserved in the token
value.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@saddlepaddle saddlepaddle merged commit f18bbb2 into main Apr 30, 2026
14 checks passed
@Kitenite Kitenite deleted the cli-jwt-apikey-fix branch May 6, 2026 04:52
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.

2 participants