Magic Link callbackurl query params are not merged #1184

Closed
opened 2026-03-13 08:26:59 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @gregg-cbs on GitHub (May 9, 2025).

Is this suited for github?

  • Yes, this is suited for github

Issue

In short the url ends up being this (noticed the two ? in the query params)

http://localhost:5173/login?from_better_auth=true?error=INVALID_TOKEN

To Reproduce

Send a magic link with a query param in callbackURL:

    const {error: err} = await authClient.signIn.magicLink({
      email,
      callbackURL: "http://localhost:5173/login?from_better_auth=true"
    });

The magic link url is this, which is correct

http://localhost:3000/api/auth/magic-link/verify?token=lqqowxkhhubcfveaxgpaztvgdmwdmsar&callbackURL=http://localhost:5173/login?authenticated=true

Better auth then adds another query param when it redirects from the verify endpoint, but as an extra query not as an extra param:

http://localhost:5173/login?from_better_auth=true?error=INVALID_TOKEN

This should be with an "&":

http://localhost:5173/login?from_better_auth=true&error=INVALID_TOKEN

Current vs. Expected behavior

Currently better auth appends query param by adding "?error=asd" when it should be appending the param by using the URL constructor.

What version of Better Auth are you using?

1.1.21

Provide environment information

Windows 10
Firefox

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

Backend, Client

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
      plugins: [
        magicLink({
          expiresIn: AUTH_MAGIC_LINK_EXPIRY_SECONDS, // 1 hour
          sendMagicLink: async ({email, url}, request) => {
            await email_login_magic_link(email, url);
          }
        }),
      ],
});
Originally created by @gregg-cbs on GitHub (May 9, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### Issue In short the url ends up being this (noticed the two ? in the query params) ``` http://localhost:5173/login?from_better_auth=true?error=INVALID_TOKEN ``` ### To Reproduce Send a magic link with a query param in callbackURL: ```ts const {error: err} = await authClient.signIn.magicLink({ email, callbackURL: "http://localhost:5173/login?from_better_auth=true" }); ``` The magic link url is this, which is correct ``` http://localhost:3000/api/auth/magic-link/verify?token=lqqowxkhhubcfveaxgpaztvgdmwdmsar&callbackURL=http://localhost:5173/login?authenticated=true ``` Better auth then adds another query param when it redirects from the verify endpoint, but as an extra query not as an extra param: ``` http://localhost:5173/login?from_better_auth=true?error=INVALID_TOKEN ``` This should be with an "&": ``` http://localhost:5173/login?from_better_auth=true&error=INVALID_TOKEN ``` ### Current vs. Expected behavior Currently better auth appends query param by adding "?error=asd" when it should be appending the param by using the URL constructor. ### What version of Better Auth are you using? 1.1.21 ### Provide environment information ```bash Windows 10 Firefox ``` ### Which area(s) are affected? (Select all that apply) Backend, Client ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ plugins: [ magicLink({ expiresIn: AUTH_MAGIC_LINK_EXPIRY_SECONDS, // 1 hour sendMagicLink: async ({email, url}, request) => { await email_login_magic_link(email, url); } }), ], }); ```
Author
Owner

@philipp-lampert commented on GitHub (May 11, 2025):

Yes, can confirm that this is an issue. Would be great to have a fix for that.

Encoding the URL with encodeURIComponent() also does not work because it is being rejected by Better-Auth as an invalid callbackURL.

I think the simplest fix would be to always encode the callbackURL inside Better-Auth and then decode it again (also inside Better-Auth).

@philipp-lampert commented on GitHub (May 11, 2025): Yes, can confirm that this is an issue. Would be great to have a fix for that. Encoding the URL with `encodeURIComponent()` also does not work because it is being rejected by Better-Auth as an invalid callbackURL. I think the simplest fix would be to always encode the callbackURL inside Better-Auth and then decode it again (also inside Better-Auth).
Author
Owner

@gregg-cbs commented on GitHub (May 12, 2025):

I see you made a pull request, thank you. I look forward to that coming out.

In the mean time i am doing the below which isnt so bad:

window.location.href.includes("from_better_auth=true")
@gregg-cbs commented on GitHub (May 12, 2025): I see you made a pull request, thank you. I look forward to that coming out. In the mean time i am doing the below which isnt so bad: ```ts window.location.href.includes("from_better_auth=true") ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1184