[GH-ISSUE #5030] signIn action callbackURL parameter doesn't work like signUp - causes redirect instead of being used in verification email #27468

Closed
opened 2026-04-17 18:30:21 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @phuctm97 on GitHub (Oct 2, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/5030

Description

The callbackURL parameter behaves inconsistently between signUp and signIn actions when email verification is required.

Expected Behavior

The callbackURL parameter in signIn.email() should work the same way as signUp.email():

  • When email verification is required, the callbackURL should be included in the verification email link
  • After clicking the email link and completing verification, the user should be redirected to the callbackURL

Actual Behavior

When using callbackURL with signIn.email():

  • The callbackURL is NOT included in the verification email link
  • Instead, it causes an immediate browser redirect on the client side
  • This breaks the expected flow where users should click the verification email first

Code Example

signUp (works as expected):

await authClient.signUp.email({
  email: "user@example.com",
  password: "password",
  callbackURL: "https://myapp.com/verify-email/callback/encoded-params"
});
// ✅ callbackURL is used in the verification email

signIn (doesn't work as expected):

await authClient.signIn.email({
  email: "user@example.com", 
  password: "password",
  callbackURL: "https://myapp.com/verify-email/callback/encoded-params"
});
// ❌ callbackURL causes immediate redirect instead of being used in email

Current Workaround

Currently, we cannot use callbackURL with signIn.email() when email verification is required. Instead, we have to:

  1. Call signIn.email() without callbackURL
  2. Manually handle the navigation after sign-in
  3. Manually check if email is verified
  4. Manually navigate to the verify-email page with params

signIn route example:

await authClient.signIn.email(values);
if (authenticated.user.emailVerified) {
  const callback = normalizeCallback(searchParams.get("callback"));
  await navigate(callback, { replace: true });
} else {
  await navigate(`/verify-email?${searchParams}`, { replace: true });
}

Steps to Reproduce

  1. Create a user with unverified email
  2. Call signIn.email() with a callbackURL parameter
  3. Observe that the client redirects immediately instead of waiting for email verification
  4. Check the verification email - the callbackURL is not included in the email link

Proposed Solution

The signIn action should handle callbackURL the same way as signUp - by including it in the verification email link when email verification is required, rather than causing an immediate client-side redirect.

This would provide a consistent API experience and reduce the amount of manual handling required in application code.

Originally created by @phuctm97 on GitHub (Oct 2, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/5030 ## Description The `callbackURL` parameter behaves inconsistently between `signUp` and `signIn` actions when email verification is required. ### Expected Behavior The `callbackURL` parameter in `signIn.email()` should work the same way as `signUp.email()`: - When email verification is required, the `callbackURL` should be included in the verification email link - After clicking the email link and completing verification, the user should be redirected to the `callbackURL` ### Actual Behavior When using `callbackURL` with `signIn.email()`: - The `callbackURL` is **NOT** included in the verification email link - Instead, it causes an immediate browser redirect on the client side - This breaks the expected flow where users should click the verification email first ### Code Example **signUp (works as expected):** ```typescript await authClient.signUp.email({ email: "user@example.com", password: "password", callbackURL: "https://myapp.com/verify-email/callback/encoded-params" }); // ✅ callbackURL is used in the verification email ``` **signIn (doesn't work as expected):** ```typescript await authClient.signIn.email({ email: "user@example.com", password: "password", callbackURL: "https://myapp.com/verify-email/callback/encoded-params" }); // ❌ callbackURL causes immediate redirect instead of being used in email ``` ### Current Workaround Currently, we cannot use `callbackURL` with `signIn.email()` when email verification is required. Instead, we have to: 1. Call `signIn.email()` without `callbackURL` 2. Manually handle the navigation after sign-in 3. Manually check if email is verified 4. Manually navigate to the verify-email page with params **signIn route example:** ```typescript await authClient.signIn.email(values); if (authenticated.user.emailVerified) { const callback = normalizeCallback(searchParams.get("callback")); await navigate(callback, { replace: true }); } else { await navigate(`/verify-email?${searchParams}`, { replace: true }); } ``` ### Steps to Reproduce 1. Create a user with unverified email 2. Call `signIn.email()` with a `callbackURL` parameter 3. Observe that the client redirects immediately instead of waiting for email verification 4. Check the verification email - the `callbackURL` is not included in the email link ### Proposed Solution The `signIn` action should handle `callbackURL` the same way as `signUp` - by including it in the verification email link when email verification is required, rather than causing an immediate client-side redirect. This would provide a consistent API experience and reduce the amount of manual handling required in application code.
GiteaMirror added the lockedenhancement labels 2026-04-17 18:30:21 -05:00
Author
Owner

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

The callbackURL parameter is included in the verification email link for both signUp.email() and signIn.email() when email verification is required, and after verification, the user is redirected to the callbackURL if provided. If the user's email is already verified, signIn.email() creates a session and immediately redirects to the callbackURL, which matches the behavior you're seeing. This means the callbackURL should work consistently in both flows, provided the backend and frontend are handling encoding correctly.

If you're seeing callbackURL not included in the verification email for signIn.email(), make sure you're using a version of Better Auth with the fix from PR #3863, which preserves encoded characters in callback URLs. For older versions, you may need to double-encode the callbackURL or manually parse parameters to avoid issues with query splitting (source).

If you still encounter issues, check that your signIn.email() call is triggering email verification (i.e., the user is not already verified), and that your callbackURL is being passed and encoded as expected. The verification email link should look like /verify-email?token=...&callbackURL=..., and after verification, the redirect should use your callbackURL (signIn.email() implementation, email verification endpoint).

To reply, just mention @dosu.


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

<!-- gh-comment-id:3358700256 --> @dosubot[bot] commented on GitHub (Oct 2, 2025): <!-- Answer --> The callbackURL parameter is included in the verification email link for both signUp.email() and signIn.email() when email verification is required, and after verification, the user is redirected to the callbackURL if provided. If the user's email is already verified, signIn.email() creates a session and immediately redirects to the callbackURL, which matches the behavior you're seeing. This means the callbackURL should work consistently in both flows, provided the backend and frontend are handling encoding correctly. If you're seeing callbackURL not included in the verification email for signIn.email(), make sure you're using a version of Better Auth with the fix from [PR #3863](https://github.com/better-auth/better-auth/pull/3863), which preserves encoded characters in callback URLs. For older versions, you may need to double-encode the callbackURL or manually parse parameters to avoid issues with query splitting ([source](https://github.com/better-auth/better-auth/issues/4780)). If you still encounter issues, check that your signIn.email() call is triggering email verification (i.e., the user is not already verified), and that your callbackURL is being passed and encoded as expected. The verification email link should look like `/verify-email?token=...&callbackURL=...`, and after verification, the redirect should use your callbackURL ([signIn.email() implementation](https://github.com/better-auth/better-auth/blob/f6cbdcc84ee5d2971fdcc8b23ff7c174f88cf45b/packages/better-auth/src/api/routes/sign-in.ts), [email verification endpoint](https://github.com/better-auth/better-auth/blob/f6cbdcc84ee5d2971fdcc8b23ff7c174f88cf45b/packages/better-auth/src/api/routes/email-verification.ts)). <!-- 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/80aeee58-48a2-42c2-b948-9ce857919cb6?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/80aeee58-48a2-42c2-b948-9ce857919cb6?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/80aeee58-48a2-42c2-b948-9ce857919cb6?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/80aeee58-48a2-42c2-b948-9ce857919cb6?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/80aeee58-48a2-42c2-b948-9ce857919cb6?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/80aeee58-48a2-42c2-b948-9ce857919cb6?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/80aeee58-48a2-42c2-b948-9ce857919cb6?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/5030)
Author
Owner

@iwantobelieve commented on GitHub (Dec 3, 2025):

TThis inconsistency is confusing, especially when you have sendOnSignIn: true enabled.

<!-- gh-comment-id:3607413763 --> @iwantobelieve commented on GitHub (Dec 3, 2025): TThis inconsistency is confusing, especially when you have sendOnSignIn: true enabled.
Author
Owner

@dosubot[bot] commented on GitHub (Mar 4, 2026):

Hi, @phuctm97. I'm Dosu, and I'm helping the better-auth team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You reported that callbackURL in signIn.email() behaves differently than in signUp.email() when email verification is required.
  • The issue was that signIn.email() caused an immediate redirect instead of including the callbackURL in the verification email link.
  • It was clarified that callbackURL is included in verification emails for both flows if the email is unverified.
  • Immediate redirect only happens if the email is already verified.
  • Using a version with PR #3863 ensures proper URL encoding and expected verification triggering.

Next Steps:

  • Please confirm if this issue is still relevant with the latest version of better-auth.
  • If it is, you can keep the discussion open by commenting on this issue; otherwise, I will automatically close it in 7 days.

Thank you for your understanding and contribution!

<!-- gh-comment-id:3998560799 --> @dosubot[bot] commented on GitHub (Mar 4, 2026): Hi, @phuctm97. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog and am marking this issue as stale. **Issue Summary:** - You reported that callbackURL in signIn.email() behaves differently than in signUp.email() when email verification is required. - The issue was that signIn.email() caused an immediate redirect instead of including the callbackURL in the verification email link. - It was clarified that callbackURL is included in verification emails for both flows if the email is unverified. - Immediate redirect only happens if the email is already verified. - Using a version with PR #3863 ensures proper URL encoding and expected verification triggering. **Next Steps:** - Please confirm if this issue is still relevant with the latest version of better-auth. - If it is, you can keep the discussion open by commenting on this issue; otherwise, I will automatically close it in 7 days. Thank you for your understanding and contribution!
Author
Owner

@github-actions[bot] commented on GitHub (Apr 1, 2026):

This issue has been locked as it was closed more than 7 days ago. If you're experiencing a similar problem or you have additional context, please open a new issue and reference this one.

<!-- gh-comment-id:4166555031 --> @github-actions[bot] commented on GitHub (Apr 1, 2026): This issue has been locked as it was closed more than 7 days ago. If you're experiencing a similar problem or you have additional context, please open a new issue and reference this one.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#27468