fix(auth): respect trustedOrigins when baseURL is inferred (#6882)

This commit is contained in:
Paola Estefanía de Campos
2025-12-19 14:08:37 -03:00
committed by GitHub
parent e9cd882f1f
commit 19d2b3a990
3 changed files with 97 additions and 6 deletions

View File

@@ -27,7 +27,11 @@ export const auth = betterAuth({
})
```
If not explicitly set, the system will check for the environment variable `process.env.BETTER_AUTH_URL`
If not explicitly set, the system will check for the environment variable `BETTER_AUTH_URL`. If that's also not set, it will be inferred from the incoming request.
<Callout type="warn">
Relying on request inference is not recommended. For security and stability, always set `baseURL` explicitly in your config or via the `BETTER_AUTH_URL` environment variable.
</Callout>
## `basePath`

View File

@@ -247,8 +247,8 @@ describe("origin check middleware", async (it) => {
});
});
describe("trustedOrigins regression tests", async (it) => {
it("should respect trustedOrigins from config array through full request flow", async () => {
describe("trusted origins with baseURL inferred from request", async (it) => {
it("should respect trustedOrigins array when baseURL is NOT in config", async () => {
const { customFetchImpl, testUser } = await getTestInstance({
trustedOrigins: ["http://my-frontend.com"],
emailAndPassword: {
@@ -280,7 +280,7 @@ describe("trustedOrigins regression tests", async (it) => {
expect(res.data?.user).toBeDefined();
});
it("should reject origins not in trustedOrigins config", async () => {
it("should reject untrusted origins even when baseURL is inferred", async () => {
const { customFetchImpl, testUser } = await getTestInstance({
trustedOrigins: ["http://my-frontend.com"],
emailAndPassword: {
@@ -311,7 +311,7 @@ describe("trustedOrigins regression tests", async (it) => {
expect(res.error?.status).toBe(403);
});
it("should respect BETTER_AUTH_TRUSTED_ORIGINS env variable through full request flow", async () => {
it("should respect BETTER_AUTH_TRUSTED_ORIGINS env when baseURL is NOT in config", async () => {
vi.stubEnv("BETTER_AUTH_TRUSTED_ORIGINS", "http://env-frontend.com");
try {
@@ -347,4 +347,90 @@ describe("trustedOrigins regression tests", async (it) => {
vi.unstubAllEnvs();
}
});
it("should allow requests from inferred baseURL origin", async () => {
const { customFetchImpl, testUser } = await getTestInstance({
emailAndPassword: {
enabled: true,
},
advanced: {
disableCSRFCheck: false,
disableOriginCheck: false,
},
});
const client = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
headers: {
origin: "http://localhost:3000",
cookie: "session=test",
},
},
});
const res = await client.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "http://localhost:3000/dashboard",
});
expect(res.data?.user).toBeDefined();
});
it("should support both config array and env var together when baseURL is inferred", async () => {
vi.stubEnv("BETTER_AUTH_TRUSTED_ORIGINS", "http://env-origin.com");
try {
const { customFetchImpl, testUser } = await getTestInstance({
trustedOrigins: ["http://config-origin.com"],
emailAndPassword: {
enabled: true,
},
advanced: {
disableCSRFCheck: false,
disableOriginCheck: false,
},
});
const client = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
headers: {
origin: "http://config-origin.com",
cookie: "session=test",
},
},
});
const res1 = await client.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "http://config-origin.com/dashboard",
});
expect(res1.data?.user).toBeDefined();
const client2 = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
headers: {
origin: "http://env-origin.com",
cookie: "session=test",
},
},
});
const res2 = await client2.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "http://env-origin.com/dashboard",
});
expect(res2.data?.user).toBeDefined();
} finally {
vi.unstubAllEnvs();
}
});
});

View File

@@ -2,6 +2,7 @@ import type { AuthContext, BetterAuthOptions } from "@better-auth/core";
import { runWithAdapter } from "@better-auth/core/context";
import { BASE_ERROR_CODES, BetterAuthError } from "@better-auth/core/error";
import { getEndpoints, router } from "../api";
import { getTrustedOrigins } from "../context/helpers";
import type { Auth } from "../types";
import { getBaseURL, getOrigin } from "../utils/url";
@@ -37,13 +38,13 @@ export const createBetterAuth = <Options extends BetterAuthOptions>(
if (baseURL) {
ctx.baseURL = baseURL;
ctx.options.baseURL = getOrigin(ctx.baseURL) || undefined;
ctx.trustedOrigins = getTrustedOrigins(ctx.options);
} else {
throw new BetterAuthError(
"Could not get base URL from request. Please provide a valid base URL.",
);
}
}
if (typeof options.trustedOrigins === "function") {
ctx.trustedOrigins = [
...ctx.trustedOrigins,