Let login work on Vercel preview deployments - #17
Merged
Conversation
Signing in on a preview failed with "Request rejected (bad origin)": originOk compared the Origin header against the hardcoded canonical ORIGIN, and a preview is served from its own hostname. Local dev hit the same wall, which is why DEVELOPMENT.md tells you to paste a session cookie instead. Fixing only that check would still leave preview login broken, because the magic link was also built from ORIGIN — you would submit the form on a preview and get an email pointing at production. Both now resolve through the set of hostnames this deployment actually answers on. That set comes from Vercel's own runtime env (VERCEL_URL, VERCEL_BRANCH_URL), never from the request, and is empty in production. Deriving it from the Host header would be host-header injection: an attacker could POST with Host: evil.com and have the victim's single-use login token emailed as a link to their domain. Cross-site POSTs stay rejected everywhere, since a browser sets Origin itself and an attacker page cannot present one of our deployment origins. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
sjmiller609
marked this pull request as ready for review
July 27, 2026 17:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Signing in on a preview deployment fails with "Request rejected (bad origin)".
originOkcompares theOriginheader against the hardcoded canonicalORIGIN, and a preview is served from its own hostname, so every login form POST is treated as cross-site. Local dev hits the same wall — which is whyDEVELOPMENT.mdtells you to paste a session cookie into devtools instead of using the login form.Two things were broken, not one
Fixing the origin check alone still leaves preview login broken: the magic link is also built from
ORIGIN(app/login/route.ts), so you'd submit the form on the preview and receive an email pointing at production. Both now resolve through the same set of hostnames this deployment actually answers on.Where that set comes from, and why it matters
The allowlist is built from Vercel's own runtime env —
VERCEL_URL(per-deployment host) andVERCEL_BRANCH_URL(branch alias) — and is empty in production, whereORIGINremains the only accepted answer. Local dev (noVERCEL_ENV) addslocalhost/127.0.0.1.It is deliberately not derived from the request's
Hostheader.linkOriginputs a single-use login token into an email, so trustingHostthere would be host-header injection: an attacker could POST withHost: evil.comand have the victim's login link delivered to their domain. An unrecognized Host always falls back toORIGIN.Cross-site POSTs stay rejected everywhere. A browser sets
Originitself, so an attacker page can never present one of our deployment origins.Verification
8 new unit tests in
lib/auth/request.test.tscover production strictness (including withVERCEL_URLpresent), preview deployment + branch hosts, localhost only outside Vercel, andlinkOriginrefusing a forged Host — including a look-alike host that merely ends with the right suffix.Reproduced and confirmed against a local dev server. Before, on
main:After, with
VERCEL_ENV=previewand the two host vars set (500 = the origin check passed and it reached the email send, which fails on a dummy Resend key):Full suite (149 tests),
tsc --noEmit,spec:checkandnext buildall pass.One thing to check on the Vercel side
This makes the app accept and complete a preview login, but a preview still needs
PLANETSCALE_URLandRESEND_API_KEYpresent in the Preview environment (not just Production), or the flow fails at the DB or email step instead. Worth confirming those are set for Preview before testing.Note that previews share whatever database
PLANETSCALE_URLpoints at, so a preview login is a real login against that data.🤖 Generated with Claude Code
Note
Medium Risk
Touches CSRF origin checks and where single-use login links are built; behavior is constrained by Vercel env rather than client Host, with new tests, but auth paths warrant careful review.
Overview
Fixes "bad origin" on Vercel previews and local dev by widening
originOkto acceptORIGINplus deployment-specific origins fromVERCEL_URL,VERCEL_BRANCH_URL, and localhost when not on Vercel; production keepsORIGINonly.Magic-link URLs in
/loginnow uselinkOrigin(req)instead of a hardcodedORIGIN, so a sign-in started on a preview finishes on that preview rather than production.linkOriginonly trusts forwarded host/proto when they match that same server-side allowlist; forged or unknown hosts fall back toORIGINso emailed tokens cannot be pointed at attacker domains.Adds
lib/auth/request.test.tscovering preview/production strictness, localhost rules, and host-forgery cases.Reviewed by Cursor Bugbot for commit f5a9c79. Bugbot is set up for automated code reviews on this repo. Configure here.