fix(auth)!: ignore x-forwarded headers by default on dynamic baseURL (#9134)

This commit is contained in:
Gustavo Valverde
2026-06-11 21:54:05 -07:00
committed by GitHub
parent 73541c1190
commit 652fa53e49
3 changed files with 46 additions and 4 deletions
+20
View File
@@ -0,0 +1,20 @@
---
"better-auth": minor
---
The dynamic `baseURL` config now ignores `x-forwarded-host` and `x-forwarded-proto` unless you set `advanced.trustedProxyHeaders: true`.
Requests using `baseURL: { allowedHosts }` now resolve the auth origin from `Host` by default, so forwarded headers cannot select another allowed host unless trusted proxy headers are enabled.
**Breaking change:** if your proxy exposes the public hostname only through `x-forwarded-host`, set `advanced.trustedProxyHeaders: true`. Deployments where the proxy rewrites `Host` to the public hostname (nginx default, Vercel, Cloudflare, and Netlify) are unaffected.
**Migration:**
```ts
betterAuth({
baseURL: { allowedHosts: [...] },
advanced: {
trustedProxyHeaders: true,
},
});
```
@@ -1029,6 +1029,27 @@ describe("dynamic baseURL resolution", () => {
expect(sharedCtx.baseURL).toBe("");
});
/**
* @see https://github.com/better-auth/better-auth/pull/9134
*/
it("should ignore x-forwarded headers when trustedProxyHeaders is not configured", async () => {
const authContext = init({
baseURL: {
allowedHosts: ["example.com", "proxy.example.com"],
},
});
const authEndpoints = toAuthEndpoints(endpoints, authContext);
const res = await authEndpoints.readBaseURL({
headers: new Headers({
host: "example.com",
"x-forwarded-host": "proxy.example.com",
"x-forwarded-proto": "http",
}),
});
expect(res.baseURL).toBe("https://example.com/api/auth");
});
it("should ignore x-forwarded-host when trustedProxyHeaders is explicitly disabled", async () => {
const authContext = init({
baseURL: {
+5 -4
View File
@@ -190,14 +190,15 @@ export function pickSource(
/**
* Returns the effective `trustedProxyHeaders` value for dynamic `baseURL`
* resolution. When the user hasn't set `advanced.trustedProxyHeaders`,
* proxy headers (`x-forwarded-host` / `x-forwarded-proto`) are trusted by
* default so deployments behind a reverse proxy work without extra config.
* resolution. Proxy headers (`x-forwarded-host` / `x-forwarded-proto`) are
* ignored unless the user opts in with `advanced.trustedProxyHeaders: true`,
* matching the static-config path. Deployments behind a reverse proxy that
* only exposes the public host via `x-forwarded-host` must set the flag.
*/
export function resolveDynamicTrustedProxyHeaders(
options: BetterAuthOptions,
): boolean {
return options.advanced?.trustedProxyHeaders ?? true;
return options.advanced?.trustedProxyHeaders ?? false;
}
/**