baseUrl overwritten by path for magic links #1627

Closed
opened 2026-03-13 08:52:15 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @olingern on GitHub (Aug 2, 2025).

Thanks for the package and work. Overall, great dev experience.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

My server and client are on separate ports:

  • server: 3000
  • client: 3001

I am seeing verify links that look like this without my base URL
http://magic-link/verify?token=oqQzaxMarTdUQWNmsFenBMXPwMdZPHIh

Digging into the magic link code, I can see it's dropping my host

Image

realBaseUrl has my host, url does not

Image

I would expect url to be both host and port aware and not simply using path

Current vs. Expected behavior

url should not drop host and port

What version of Better Auth are you using?

1.3.4

Provide environment information

- OS: OSX 15.5
- Chrome 138.0.7204.169 (

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "../db";
import { magicLink, oAuthProxy, openAPI } from "better-auth/plugins";
import { config } from "@/lib/config";

export const auth = betterAuth({
  basePath: "/",
  database: drizzleAdapter(db, {
    provider: "pg",
  }),
  secret: config.betterAuthSecret,
  baseURL: config.betterAuthUrl,
  trustedOrigins: [config.clientUrl],
  emailAndPassword: {
    enabled: true,
  },
  session: {
    expiresIn: 60 * 60 * 24 * 7,
    updateAge: 60 * 60 * 24,
    cookieCache: {
      enabled: true,
      maxAge: 60 * 5,
    },
  },
  advanced: {
    crossSubDomainCookies: {
      enabled: true,
    },
  },
  security: {
    csrf: {
      disabled: true,
    },
  },
  plugins: [
    openAPI(),
    magicLink({
      expiresIn: 60 * 60, // 1 hour
      rateLimit: {
        window: 60, // 1 minute
        max: 5, // 5 requests per minute
      },

      storeToken: "hashed", // Store tokens as hashed in the database
      sendMagicLink: async ({ email, url }) => {
        console.log("Magic link sent to:", email);
        console.log("Magic link URL:", url);
      },
    }),
    oAuthProxy({
      productionURL: config.clientUrl,
      currentURL: config.betterAuthUrl,
    }),
  ],
  emailVerification: {
    sendOnSignUp: true,
    autoSignInAfterVerification: true,

    sendVerificationEmail: async ({ user, url }) => {
      console.log("Email verification link:", url, user.email);
    },
    afterEmailVerification: async (user, request) => {
      console.log("Email verified for user:", user.email);
    },
  },
});

Additional context

I would be open to creating a PR, but am unsure of why path was chosen. FWIW, GPT also says the usage is incorrect

Originally created by @olingern on GitHub (Aug 2, 2025). Thanks for the package and work. Overall, great dev experience. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce My server and client are on separate ports: - server: 3000 - client: 3001 I am seeing verify links that look like this without my base URL `http://magic-link/verify?token=oqQzaxMarTdUQWNmsFenBMXPwMdZPHIh` Digging into the magic link code, I can see it's dropping my `host` <img width="996" height="1017" alt="Image" src="https://github.com/user-attachments/assets/92535c02-8327-4e85-8f1b-34cba431f96b" /> `realBaseUrl` has my `host`, `url` does not <img width="968" height="799" alt="Image" src="https://github.com/user-attachments/assets/e25d26a8-d4eb-40ea-9249-3c1832e4256f" /> ___ I would expect `url` to be both host and port aware and not simply using `path` ### Current vs. Expected behavior `url` should not drop `host` and `port` ### What version of Better Auth are you using? 1.3.4 ### Provide environment information ```bash - OS: OSX 15.5 - Chrome 138.0.7204.169 ( ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { db } from "../db"; import { magicLink, oAuthProxy, openAPI } from "better-auth/plugins"; import { config } from "@/lib/config"; export const auth = betterAuth({ basePath: "/", database: drizzleAdapter(db, { provider: "pg", }), secret: config.betterAuthSecret, baseURL: config.betterAuthUrl, trustedOrigins: [config.clientUrl], emailAndPassword: { enabled: true, }, session: { expiresIn: 60 * 60 * 24 * 7, updateAge: 60 * 60 * 24, cookieCache: { enabled: true, maxAge: 60 * 5, }, }, advanced: { crossSubDomainCookies: { enabled: true, }, }, security: { csrf: { disabled: true, }, }, plugins: [ openAPI(), magicLink({ expiresIn: 60 * 60, // 1 hour rateLimit: { window: 60, // 1 minute max: 5, // 5 requests per minute }, storeToken: "hashed", // Store tokens as hashed in the database sendMagicLink: async ({ email, url }) => { console.log("Magic link sent to:", email); console.log("Magic link URL:", url); }, }), oAuthProxy({ productionURL: config.clientUrl, currentURL: config.betterAuthUrl, }), ], emailVerification: { sendOnSignUp: true, autoSignInAfterVerification: true, sendVerificationEmail: async ({ user, url }) => { console.log("Email verification link:", url, user.email); }, afterEmailVerification: async (user, request) => { console.log("Email verified for user:", user.email); }, }, }); ``` ### Additional context I would be open to creating a PR, but am unsure of why `path` was chosen. FWIW, GPT also says the usage is incorrect
GiteaMirror added the plugin label 2026-03-13 08:52:15 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Aug 2, 2025):

Hi @olingern! I'm Dosu and I’m helping the better-auth team.

The magic link URL construction in Better Auth (including 1.3.4) uses the baseURL from your config and should always include the host and port if baseURL is set correctly. The code combines the baseURL's origin (host/port) with the path using the URL constructor, so if you're seeing URLs like http://magic-link/verify?..., it usually means baseURL is not being picked up as expected, often due to a config or environment mismatch—not a bug in the plugin itself [code reference].

Common causes:

  • The property must be named baseURL (uppercase "URL"), not baseUrl—otherwise, it's ignored [source].
  • If you set both baseURL (with a path) and basePath, basePath is ignored. Use either a full baseURL (with path) or baseURL + basePath, but not both [source].
  • If you're behind a proxy or using a custom Express mount path, the path in baseURL must match exactly what Express sees. If a proxy strips or rewrites the path, URLs can break or lose the host/port [source].

To debug:

  • Log out the value of ctx.context.baseURL at runtime to confirm what Better Auth is actually using.
  • Double-check your config for typos, case sensitivity, and path mismatches.
  • If using a reverse proxy, make sure it preserves the full path and host, or adjust your Express mount path and baseURL to match what Express receives.

If all config looks correct but the issue persists, try isolating the problem in a minimal example or share the runtime value of ctx.context.baseURL for further diagnosis.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Aug 2, 2025): <!-- Greeting --> Hi @olingern! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The magic link URL construction in Better Auth (including 1.3.4) uses the baseURL from your config and should always include the host and port if baseURL is set correctly. The code combines the baseURL's origin (host/port) with the path using the URL constructor, so if you're seeing URLs like `http://magic-link/verify?...`, it usually means baseURL is not being picked up as expected, often due to a config or environment mismatch—not a bug in the plugin itself [[code reference]](https://github.com/better-auth/better-auth/blob/987be984b4d840a409ca1e0d6098ee0f3303bcb6/packages/better-auth/src/plugins/magic-link/index.ts). Common causes: - The property must be named `baseURL` (uppercase "URL"), not `baseUrl`—otherwise, it's ignored [[source]](https://github.com/better-auth/better-auth/issues/2875). - If you set both baseURL (with a path) and basePath, basePath is ignored. Use either a full baseURL (with path) or baseURL + basePath, but not both [[source]](https://github.com/better-auth/better-auth/issues/2710). - If you're behind a proxy or using a custom Express mount path, the path in baseURL must match exactly what Express sees. If a proxy strips or rewrites the path, URLs can break or lose the host/port [[source]](https://github.com/better-auth/better-auth/issues/3215). To debug: - Log out the value of `ctx.context.baseURL` at runtime to confirm what Better Auth is actually using. - Double-check your config for typos, case sensitivity, and path mismatches. - If using a reverse proxy, make sure it preserves the full path and host, or adjust your Express mount path and baseURL to match what Express receives. If all config looks correct but the issue persists, try isolating the problem in a minimal example or share the runtime value of `ctx.context.baseURL` for further diagnosis. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/52cd2eca-ffa0-46c2-8ff0-7e4ac4d450d4?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/52cd2eca-ffa0-46c2-8ff0-7e4ac4d450d4?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/52cd2eca-ffa0-46c2-8ff0-7e4ac4d450d4?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/52cd2eca-ffa0-46c2-8ff0-7e4ac4d450d4?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/52cd2eca-ffa0-46c2-8ff0-7e4ac4d450d4?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/52cd2eca-ffa0-46c2-8ff0-7e4ac4d450d4?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/52cd2eca-ffa0-46c2-8ff0-7e4ac4d450d4?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/3759)
Author
Owner

@olingern commented on GitHub (Aug 2, 2025):

@dosu baseUrl is set correctly in my config. It looks like a bug

auth setup
Image

config
Image

.env

Image
@olingern commented on GitHub (Aug 2, 2025): @dosu `baseUrl` is set correctly in my config. It looks like a bug auth setup <img width="968" height="799" alt="Image" src="https://github.com/user-attachments/assets/77a0ed32-f404-4c0f-b1d6-6a92ac827d8b" /> config <img width="968" height="799" alt="Image" src="https://github.com/user-attachments/assets/9b43b35c-be6b-47d6-9d5d-489c995c6317" /> .env <img width="968" height="799" alt="Image" src="https://github.com/user-attachments/assets/a079a421-6253-46cb-ac89-ab188b613284" />
Author
Owner

@dosubot[bot] commented on GitHub (Aug 2, 2025):

The magic link plugin always uses the baseURL (with uppercase "URL") from your config to construct URLs, and the code and tests confirm that host and port are included if baseURL is set correctly (code reference). If you're seeing URLs like http://magic-link/verify?..., the most likely causes are:

  • The config property is named baseUrl (lowercase "u") somewhere, which is ignored—only baseURL (uppercase "URL") is valid (see docs).
  • The environment variable isn't mapped correctly, or the value passed to betterAuth isn't a full URL (e.g., it's just a path).
  • If you're behind a proxy or using a custom mount path, the path in baseURL must match exactly what your server sees, or URLs can break (proxy/path details).

To debug, log the runtime value of ctx.context.baseURL inside your magic link handler. If it isn't the full URL with host and port, double-check your config, environment variable mapping, and spelling/casing everywhere. If it is correct at runtime and the issue persists, share the log output or a minimal repro so we can dig deeper.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Aug 2, 2025): <!-- Answer --> The magic link plugin always uses the `baseURL` (with uppercase "URL") from your config to construct URLs, and the code and tests confirm that host and port are included if `baseURL` is set correctly ([code reference](https://github.com/better-auth/better-auth/blob/987be984b4d840a409ca1e0d6098ee0f3303bcb6/packages/better-auth/src/plugins/magic-link/index.ts)). If you're seeing URLs like `http://magic-link/verify?...`, the most likely causes are: - The config property is named `baseUrl` (lowercase "u") somewhere, which is ignored—only `baseURL` (uppercase "URL") is valid ([see docs](https://github.com/better-auth/better-auth/issues/3215)). - The environment variable isn't mapped correctly, or the value passed to `betterAuth` isn't a full URL (e.g., it's just a path). - If you're behind a proxy or using a custom mount path, the path in `baseURL` must match exactly what your server sees, or URLs can break ([proxy/path details](https://github.com/better-auth/better-auth/issues/3215)). To debug, log the runtime value of `ctx.context.baseURL` inside your magic link handler. If it isn't the full URL with host and port, double-check your config, environment variable mapping, and spelling/casing everywhere. If it is correct at runtime and the issue persists, share the log output or a minimal repro so we can dig deeper. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/e66d2635-2e15-4055-b97c-8363dcaab156?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/e66d2635-2e15-4055-b97c-8363dcaab156?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/e66d2635-2e15-4055-b97c-8363dcaab156?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/e66d2635-2e15-4055-b97c-8363dcaab156?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/e66d2635-2e15-4055-b97c-8363dcaab156?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/e66d2635-2e15-4055-b97c-8363dcaab156?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/e66d2635-2e15-4055-b97c-8363dcaab156?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/3759)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1627