fix: proxy Neon Auth through Express to resolve Better Auth CSRF origin errors - #83
Conversation
…in errors - Rename VITE_NEON_AUTH_URL → NEON_AUTH_URL (server-side only, no VITE_ prefix) - Add Express proxy middleware in server/index.js that forwards all /api/auth/* requests upstream to Neon, omitting the Origin header so Better Auth's CSRF check does not reject them as cross-origin - Update neonAuth.js client to route through window.location.origin (same-origin proxy) instead of calling Neon directly - Update .env.example and README.md to reflect the new env var and architecture
- README: rewrite to match actual stack (Neon Auth, Kysely, Stockfish 18, Vercel serverless, correct scripts table, full repo structure tree) - README: fix NEON_AUTH_URL env var (was incorrectly VITE_NEON_AUTH_URL) - server/index.js: add Express proxy middleware for Better Auth /api/auth/* routes to resolve cross-origin CSRF errors
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe PR renames ChangesNeon Auth server-side proxy
Sequence Diagram(s)sequenceDiagram
participant Browser
participant Express as Express (/api/auth/*)
participant NeonAuth as Neon Auth upstream
rect rgba(135, 206, 250, 0.5)
note over Browser,NeonAuth: Auth request via same-origin proxy
Browser->>Express: POST /api/auth/sign-in<br/>(cookie, authorization headers)
Express->>NeonAuth: Forward method, body, query params<br/>(origin header omitted)
NeonAuth-->>Express: Response status + headers + body
Express-->>Browser: Relay upstream response<br/>(strip transfer-encoding, connection)
end
rect rgba(255, 160, 122, 0.5)
note over Browser,Express: Proxy failure path
Express->>NeonAuth: Upstream fetch
NeonAuth--xExpress: Network/timeout error
Express-->>Browser: 502 JSON error
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a44c21103
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ? '?' + new URLSearchParams(req.query).toString() | ||
| : ''; | ||
| const upstreamRes = await fetch( | ||
| `${_neonAuthProxyUrl}/api/auth${req.path}${qs}`, |
There was a problem hiding this comment.
Use the configured Neon Auth base path
When NEON_AUTH_URL is copied from the Neon Console as the updated docs/env comments instruct, it is already the Auth Base URL (for example ...neon.build/neondb/auth). Since the Better Auth client reaches Express at /api/auth/... and this middleware is mounted on /api/auth, req.path is already the remaining endpoint path; appending another /api/auth sends OTP/session requests to .../neondb/auth/api/auth/..., which Neon will not serve and leaves sign-in broken. Proxy to the configured base plus req.path (or normalize only true bare origins) instead.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
README.md (1)
45-73: ⚡ Quick winVerify environment variable naming and fallback behavior align with configuration guidance.
The Configuration section prescribes
NEON_AUTH_URLas the required server-side variable (line 54), butserver/index.jsincludes a fallback toVITE_NEON_AUTH_URLfor backwards compatibility. Consider clarifying in the README whether developers migrating from the old setup should:
- Remove
VITE_NEON_AUTH_URLand use onlyNEON_AUTH_URL, or- Keep the old variable temporarily during migration.
This note would help prevent confusion for users upgrading to the new proxy architecture.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.md` around lines 45 - 73, The README Configuration section documents NEON_AUTH_URL as the required variable but does not mention that server/index.js maintains a backwards compatibility fallback to VITE_NEON_AUTH_URL. Add a clarification note in the Configuration section explaining the backwards compatibility fallback behavior and guidance for users: either they can remove the old VITE_NEON_AUTH_URL variable and migrate to NEON_AUTH_URL, or they can keep the old variable temporarily during migration. This will prevent confusion for users upgrading to the new architecture.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@README.md`:
- Line 76: The fenced code block containing the repository structure in
README.md is missing a language specifier, which violates the markdownlint MD040
rule. Locate the opening triple backticks (```) that precede the repository
structure content (with directories like chess.com-app/, api/, etc.) and add a
language identifier such as "text" or "bash" immediately after the opening
backticks (e.g., ```text). This will ensure the code block is properly formatted
according to markdown linting standards.
In `@server/index.js`:
- Around line 229-234: The generic header iteration in the
upstreamRes.headers.forEach loop is collapsing multiple Set-Cookie headers into
a single value, breaking authentication flows that require multiple session
cookies. Add 'set-cookie' to the exclusion condition alongside
'transfer-encoding' and 'connection' in the if statement, then after the loop,
separately retrieve all Set-Cookie headers using upstreamRes.getSetCookie() and
properly set them on the response using res.setHeader or res.appendHeader to
preserve multiple cookie values.
- Around line 211-227: The fetch call to the Neon auth proxy (the upstreamRes
assignment) lacks a timeout mechanism, which can cause requests to hang
indefinitely if the upstream service stalls. Implement an abort timeout by
creating an AbortController, setting a timeout that calls abort() if the fetch
operation exceeds a reasonable duration (e.g., 30 seconds), and passing the
controller's signal to the fetch options. Additionally, handle the AbortError
that may be thrown when the timeout triggers, ensuring it is caught and logged
appropriately in the existing try-catch block.
---
Nitpick comments:
In `@README.md`:
- Around line 45-73: The README Configuration section documents NEON_AUTH_URL as
the required variable but does not mention that server/index.js maintains a
backwards compatibility fallback to VITE_NEON_AUTH_URL. Add a clarification note
in the Configuration section explaining the backwards compatibility fallback
behavior and guidance for users: either they can remove the old
VITE_NEON_AUTH_URL variable and migrate to NEON_AUTH_URL, or they can keep the
old variable temporarily during migration. This will prevent confusion for users
upgrading to the new architecture.
🪄 Autofix (Beta)
✅ Autofix completed
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 946465e7-a943-4f8a-9e50-537205425abd
📒 Files selected for processing (4)
.env.exampleREADME.mdserver/index.jssrc/services/neonAuth.js
| ## 📁 Repository Structure | ||
|
|
||
| *Confidential. Internal use only.* | ||
| ``` |
There was a problem hiding this comment.
Add language specifier to fenced code block.
The repository structure code block is missing a language identifier required by markdownlint. Add a language specifier (e.g., ```text or ```bash) to satisfy the MD040 rule.
💡 Proposed fix
-```
+```text
chess.com-app/
├── api/🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 76-76: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` at line 76, The fenced code block containing the repository
structure in README.md is missing a language specifier, which violates the
markdownlint MD040 rule. Locate the opening triple backticks (```) that precede
the repository structure content (with directories like chess.com-app/, api/,
etc.) and add a language identifier such as "text" or "bash" immediately after
the opening backticks (e.g., ```text). This will ensure the code block is
properly formatted according to markdown linting standards.
Source: Linters/SAST tools
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. Fixes Applied SuccessfullyFixed 1 file(s) based on 2 unresolved review comments. Files modified:
Commit: The changes have been pushed to the Time taken: |
Fixed 1 file(s) based on 2 unresolved review comments. Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Problem
Better Auth (Neon) validates the browser's
Originheader as a CSRF guard. When the frontend runs on a different origin from the Neon Auth URL (e.g.localhost:5173in dev, or a Vercel URL in prod), auth requests are rejected with an "Invalid origin" error.Solution
Proxy all Better Auth calls through Express. Server-side
fetchcalls don't carry a browserOriginheader, so Neon's CSRF check passes. The frontend now routes auth requests to the same origin (/api/auth/*), which Express proxies upstream to Neon.Changes
server/index.js_neonAuthProxymiddleware that forwards all/api/auth/*requests to the Neon Auth URL upstreamcookieandauthorizationheaders; intentionally omitsOrigin(the fix)/sessionand/signoutwhich are handled by the customauthRouterNEON_AUTH_URLis not setsrc/services/neonAuth.jscreateAuthClientnow points towindow.location.origin(same-origin proxy) instead of directly toVITE_NEON_AUTH_URLif (!neonAuthUrl)block — client always initialises; proxy handles availability.env.exampleVITE_NEON_AUTH_URL→NEON_AUTH_URL(server-side only, noVITE_prefix needed)README.mdNEON_AUTH_URLTesting
npm run dev— OTP sign-in flow works end-to-end (no "Invalid origin" error)NEON_AUTH_URLunset → server logs warning, auth gracefully unavailableNotes
The docs research confirmed
createAuthClient({ baseURL: window.location.origin })is the correct pattern for a same-origin proxy. Theredirect: 'manual'on the upstream fetch prevents Express from following Neon redirects blindly and returning an opaque response to the browser.Summary by CodeRabbit
Documentation
Chores / Configuration
VITE_NEON_AUTH_URLtoNEON_AUTH_URL.New Features
Chores / Improvements