Skip to content

fix(auth): read the client-facing host, not x-forwarded-host - #74

Merged
ralyodio merged 1 commit into
masterfrom
fix/oauth-host-header-precedence
Jul 31, 2026
Merged

fix(auth): read the client-facing host, not x-forwarded-host#74
ralyodio merged 1 commit into
masterfrom
fix/oauth-host-header-precedence

Conversation

@ralyodio

Copy link
Copy Markdown
Collaborator

Follow-up to #73, which shipped but had no effect in production — every host still resolved to APP_BASE_URL:

$ curl -sI https://pit.moshcode.sh/auth/login
redirect_uri=https%3A%2F%2Fmoshcoding.com%2Fauth%2Fcoinpay%2Fcallback   # wrong

Cause

requestHost preferred x-forwarded-host. Railway sets that to the canonical service domain, not the requested one — so a request to pit.moshcode.sh arrived carrying moshcoding.up.railway.app, which isn't allowlisted, so it fell back. The allowlist worked exactly as designed; it was just fed the wrong input.

host is the client-facing name on this platform. Proof: the www → apex redirect in middleware.ts:26 reads req.headers.get("host") and works on every domain the service answers on:

$ curl -sI https://www.moshcode.sh/   → 308 https://moshcode.sh/
$ curl -sI https://www.moshcoding.com/ → 308 https://moshcoding.com/

Fix

requestHosts() returns both candidates, host first. resolveOrigin() takes a list and picks the first allowlisted one.

Keeping x-forwarded-host as a second candidate covers proxies that behave the other way round. Trying both is safe because the allowlist is the security boundary, not header precedence — forging either header only lets an attacker select among hostnames we already serve and have already registered with the IdP, and neither matching still falls back to APP_BASE_URL.

Verification

  • bun test tests/65 pass, 0 fail (3 new, including the exact Railway header shape that caused this)
  • tsc --noEmit → clean
  • bun run build → clean

🤖 Generated with Claude Code

The per-host redirect_uri landed but had no effect in production: every
host still resolved to APP_BASE_URL.

requestHost preferred x-forwarded-host. Railway sets that to the
canonical service domain rather than the domain the client asked for, so
a request to pit.moshcode.sh arrived carrying moshcoding.up.railway.app.
That is not on the allowlist, so it fell back -- exactly as designed,
just fed the wrong input.

`host` is the client-facing name here, which the www -> apex redirect in
middleware.ts already depends on and which works on all twelve domains
this service answers on. So try `host` first and keep x-forwarded-host as
a second candidate for proxies that behave the other way.

Trying both is safe because the allowlist, not header precedence, is the
security boundary: an attacker forging either header can only select
among hostnames we already serve and have already registered with the
IdP, and both still fall back to APP_BASE_URL when neither matches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio
ralyodio merged commit e80873f into master Jul 31, 2026
2 checks passed
@ralyodio
ralyodio deleted the fix/oauth-host-header-precedence branch July 31, 2026 02:30
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.

1 participant