Reset password not working, changing email is flawed #593

Closed
opened 2026-03-13 07:55:32 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @Maydara86 on GitHub (Jan 27, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Issue 1: Changing Email

  1. Trigger an email change request
  2. Click the link sent to your new email address to verify it
  3. New email address is saved to the database, but it resets EmailVerified to false
  4. A second email is sent to you to verify the address again, click on it
  5. Your new email address is verified

Issue 2: Password Reset

  1. Email and password method
  2. Reset password
  3. Email is sent to the user with link that expires in 1 hour
  4. The reset fails with error 400 bad request message: invalid token

Code of reset password onSubmit formHandler (client component)

const onSubmit = async (data: z.infer<typeof resetPasswordSchema>) => {
    setIsPending(true)
    const { error } = await authClient.resetPassword({
      newPassword: data.password,
    })
    if (error) {
      toast({
        title: "Error",
        description: error.message,
        variant: "destructive",
      })
    } else {
      toast({
        title: "Success",
        description: "Password reset successful. Login to continue.",
      })
      router.push("/sign-in")
    }
    setIsPending(false)
  }

in this screenshot the token is valid you can see it in the query params in the URL 👇
Image

and here, the link has expired which makes the token invalid, see it in the URL 👇

Image

Current vs. Expected behavior

Issue 1: Changing Email

EmailVerified shouldn't be set to false when we click on the link to verify the new email address, thus we wouldn't get a second verification email

Issue 2: Password Reset

When I want to reset the password and the token is valid, it should reset the password.

What version of Better Auth are you using?

1.1.14

Provide environment information

- Node.js 22.12.0
- Pnpm 9.15.4
- Next.js 15.1.0
- Prisma 6.1.0
- Postgres version 17 on Neon
- Windows 11, application runing on Ubuntu 20.04.3 LTS through wsl2
- Browser: Google Chrome

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

Backend

Auth config (if applicable)

import { BetterAuthOptions, betterAuth } from "better-auth"
import { prismaAdapter } from "better-auth/adapters/prisma"
import { admin, openAPI } from "better-auth/plugins"
import { passkey } from "better-auth/plugins/passkey"

import { sendEmail } from "@/actions/email"
import prisma from "@/lib/prisma"

import { getScopedI18n } from "../locales/server"

export const auth = betterAuth({
  appName: process.env.APP_NAME,
  database: prismaAdapter(prisma, {
    provider: "postgresql",
  }),
  plugins: [
    ...(process.env.NODE_ENV === "development" ? [openAPI()] : []),
    passkey(),
    admin({
      impersonationSessionDuration: 20 * 60 * 24,
    }),
  ],
  session: {
    expiresIn: 60 * 60 * 24 * 7,
    updateAge: 60 * 60 * 24,
  },
  user: {
    additionalFields: {
      hasPasskey: {
        type: "boolean",
        required: false,
      },
    },
    changeEmail: {
      enabled: true,
      sendChangeEmailVerification: async ({ newEmail, url }) => {
        const t = await getScopedI18n("auth.emails.verifyNewEmail")
        await sendEmail({
          to: newEmail,
          subject: `${process.env.APP_NAME} ${t("subject")}`,
          text: t("text", { url }),
        })
      },
    },
    deleteUser: {
      enabled: true,
      sendDeleteAccountVerification: async ({ user, url }) => {
        const t = await getScopedI18n("auth.emails.requestToDeleteUser")
        await sendEmail({
          to: user.email,
          subject: `${process.env.APP_NAME} ${t("subject")}`,
          text: t("text", { url }),
        })
      },
      afterDelete: async (user) => {
        const t = await getScopedI18n("auth.emails.deleteUser")
        await sendEmail({
          to: user.email,
          subject: `${process.env.APP_NAME} ${t("subject")}`,
          text: t("text"),
        })
      },
    },
  },

  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    sendResetPassword: async ({ user, url }) => {
      const t = await getScopedI18n("auth.emails.resetPassword")
      await sendEmail({
        to: user.email,
        subject: `${process.env.APP_NAME} ${t("subject")}`,
        text: t("text", { url }),
      })
    },
  },
  emailVerification: {
    sendOnSignUp: true,
    autoSignInAfterVerification: true,
    sendVerificationEmail: async ({ user, token }) => {
      const verificationUrl = `${process.env.BETTER_AUTH_URL}/api/auth/verify-email?token=${token}&callbackURL=${process.env.EMAIL_VERIFICATION_CALLBACK_URL}`
      const t = await getScopedI18n("auth.emails.verifyEmail")
      await sendEmail({
        to: user.email,
        subject: `${process.env.APP_NAME} ${t("subject")}`,
        text: t("text", { url: verificationUrl }),
      })
    },
  },
} satisfies BetterAuthOptions)

export type Session = typeof auth.$Infer.Session

Additional context

No response

Originally created by @Maydara86 on GitHub (Jan 27, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce ### Issue 1: Changing Email 1. Trigger an email change request 2. Click the link sent to your new email address to verify it 3. New email address is saved to the database, but it resets `EmailVerified` to false 4. A second email is sent to you to verify the address again, click on it 5. Your new email address is verified ### Issue 2: Password Reset 1. Email and password method 2. Reset password 3. Email is sent to the user with link that expires in 1 hour 4. The reset fails with error 400 bad request message: invalid token Code of reset password onSubmit formHandler (client component) ```typescript const onSubmit = async (data: z.infer<typeof resetPasswordSchema>) => { setIsPending(true) const { error } = await authClient.resetPassword({ newPassword: data.password, }) if (error) { toast({ title: "Error", description: error.message, variant: "destructive", }) } else { toast({ title: "Success", description: "Password reset successful. Login to continue.", }) router.push("/sign-in") } setIsPending(false) } ``` **in this screenshot the token is valid you can see it in the query params in the URL 👇** ![Image](https://github.com/user-attachments/assets/37a236fc-f578-4116-a33f-810317d64515) **and here, the link has expired which makes the token invalid, see it in the URL 👇** ![Image](https://github.com/user-attachments/assets/3edefae1-a316-461b-ac88-465d1838e127) ### Current vs. Expected behavior ### Issue 1: Changing Email `EmailVerified` shouldn't be set to `false` when we click on the link to verify the new email address, thus we wouldn't get a second verification email ### Issue 2: Password Reset When I want to reset the password and the token is valid, it should reset the password. ### What version of Better Auth are you using? 1.1.14 ### Provide environment information ```bash - Node.js 22.12.0 - Pnpm 9.15.4 - Next.js 15.1.0 - Prisma 6.1.0 - Postgres version 17 on Neon - Windows 11, application runing on Ubuntu 20.04.3 LTS through wsl2 - Browser: Google Chrome ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { BetterAuthOptions, betterAuth } from "better-auth" import { prismaAdapter } from "better-auth/adapters/prisma" import { admin, openAPI } from "better-auth/plugins" import { passkey } from "better-auth/plugins/passkey" import { sendEmail } from "@/actions/email" import prisma from "@/lib/prisma" import { getScopedI18n } from "../locales/server" export const auth = betterAuth({ appName: process.env.APP_NAME, database: prismaAdapter(prisma, { provider: "postgresql", }), plugins: [ ...(process.env.NODE_ENV === "development" ? [openAPI()] : []), passkey(), admin({ impersonationSessionDuration: 20 * 60 * 24, }), ], session: { expiresIn: 60 * 60 * 24 * 7, updateAge: 60 * 60 * 24, }, user: { additionalFields: { hasPasskey: { type: "boolean", required: false, }, }, changeEmail: { enabled: true, sendChangeEmailVerification: async ({ newEmail, url }) => { const t = await getScopedI18n("auth.emails.verifyNewEmail") await sendEmail({ to: newEmail, subject: `${process.env.APP_NAME} ${t("subject")}`, text: t("text", { url }), }) }, }, deleteUser: { enabled: true, sendDeleteAccountVerification: async ({ user, url }) => { const t = await getScopedI18n("auth.emails.requestToDeleteUser") await sendEmail({ to: user.email, subject: `${process.env.APP_NAME} ${t("subject")}`, text: t("text", { url }), }) }, afterDelete: async (user) => { const t = await getScopedI18n("auth.emails.deleteUser") await sendEmail({ to: user.email, subject: `${process.env.APP_NAME} ${t("subject")}`, text: t("text"), }) }, }, }, emailAndPassword: { enabled: true, requireEmailVerification: true, sendResetPassword: async ({ user, url }) => { const t = await getScopedI18n("auth.emails.resetPassword") await sendEmail({ to: user.email, subject: `${process.env.APP_NAME} ${t("subject")}`, text: t("text", { url }), }) }, }, emailVerification: { sendOnSignUp: true, autoSignInAfterVerification: true, sendVerificationEmail: async ({ user, token }) => { const verificationUrl = `${process.env.BETTER_AUTH_URL}/api/auth/verify-email?token=${token}&callbackURL=${process.env.EMAIL_VERIFICATION_CALLBACK_URL}` const t = await getScopedI18n("auth.emails.verifyEmail") await sendEmail({ to: user.email, subject: `${process.env.APP_NAME} ${t("subject")}`, text: t("text", { url: verificationUrl }), }) }, }, } satisfies BetterAuthOptions) export type Session = typeof auth.$Infer.Session ``` ### Additional context _No response_
GiteaMirror added the stalebug labels 2026-03-13 07:55:32 -05:00
Author
Owner

@jamesjulich commented on GitHub (Feb 3, 2025):

Hello

2 things:

Firstly, this should probably be 2 Github issues since the problems you're describing are likely independent problems. From a maintainer perspective, problems don't always get solved at the same speed/time. Having two separate issues allows the problems to be tracked more accurately.

Second, your issues with reset password are likely caused by a breaking change that happened when you upgraded better-auth. In version 1.1.12, better-auth stopped supplying the currentURL parameter to some endpoints. As a result, when you're resetting a password, you have to manually specify the token to the reset password endpoint now. Check the docs for the updated function call. Check out the commit history for the forget-password.ts route to see the breaking change I'm talking about.

@jamesjulich commented on GitHub (Feb 3, 2025): Hello 2 things: Firstly, this should probably be 2 Github issues since the problems you're describing are likely independent problems. From a maintainer perspective, problems don't always get solved at the same speed/time. Having two separate issues allows the problems to be tracked more accurately. Second, your issues with reset password are likely caused by a breaking change that happened when you upgraded better-auth. In version 1.1.12, better-auth stopped supplying the currentURL parameter to some endpoints. As a result, when you're resetting a password, you have to manually specify the token to the reset password endpoint now. Check the docs for the updated function call. Check out the commit history for the forget-password.ts route to see the breaking change I'm talking about.
Author
Owner

@dosubot[bot] commented on GitHub (Jun 13, 2025):

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

Issue Summary:

  • You reported two issues with Better Auth version 1.1.14: a redundant second email verification and a "400 bad request" error during password reset.
  • Maintainer jamesjulich suggested splitting the issues for better tracking.
  • The password reset issue might be linked to a breaking change in version 1.1.12, requiring manual token specification.

Next Steps:

  • Please confirm if these issues are still relevant to the latest version of the better-auth repository by commenting here.
  • If there is no response, this issue will be automatically closed in 7 days.

Thank you for your understanding and contribution!

@dosubot[bot] commented on GitHub (Jun 13, 2025): Hi, @Maydara86. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog. I'm marking this issue as stale. **Issue Summary:** - You reported two issues with Better Auth version 1.1.14: a redundant second email verification and a "400 bad request" error during password reset. - Maintainer jamesjulich suggested splitting the issues for better tracking. - The password reset issue might be linked to a breaking change in version 1.1.12, requiring manual token specification. **Next Steps:** - Please confirm if these issues are still relevant to the latest version of the better-auth repository by commenting here. - If there is no response, this issue will be automatically closed in 7 days. Thank you for your understanding and contribution!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#593