Handling error of forgetPassword call

Hi, I'm trying to handle the error when using forgetPassword but if I provide an email which is unknown, I can't catch the error. I'm seing this in the output: 2025-04-13T20:05:20.175Z ERROR [Better Auth]: Reset Password: User not found { email: '[email protected]' } Here is my code:
const handleResetRequest = async () => {
setLoading(true);
const response = await authClient.forgetPassword({ email, redirectTo: "/auth/forget-password" }, {
onError: (error) => {
toast.error(error.error.message);
},
onSuccess: () => {
toast.success("Password reset email sent! Check your inbox.");
router.push("/auth/login");
}
});
};
const handleResetRequest = async () => {
setLoading(true);
const response = await authClient.forgetPassword({ email, redirectTo: "/auth/forget-password" }, {
onError: (error) => {
toast.error(error.error.message);
},
onSuccess: () => {
toast.success("Password reset email sent! Check your inbox.");
router.push("/auth/login");
}
});
};
The code in the onSuccess is executed.
5 Replies
Glen Kurio
Glen Kurio2w ago
onError: (error) => {
toast.error(error.error.message);
},
onError: (error) => {
toast.error(error.error.message);
},
Has to be:
ts
onError: (ctx) => {
toast.error(ctx.error.message);
},
ts
onError: (ctx) => {
toast.error(ctx.error.message);
},
KiNFiSH
KiNFiSH2w ago
it is just meant for security purpose on not hoisting the error to your app instead it is logged on your server
maito
maitoOP2w ago
For what reason? To not disclose the fact that the email doesn’t exist? Shouldn’t this be the app owner to decide what to disclose or not? I mean we should be able to catch the error and display that something went wrong instead of having a successful output on this Wdyt?
bekacru
bekacru7d ago
For forgot password use cases, your app should display "If your account exists, we've sent a password reset link to your email address" we don’t expose whether the account exists to the client, to avoid email enumeration attacks
Glen Kurio
Glen Kurio7d ago
Rate limit is not applied to /forget-password path for some reason. Despite I have this custom rule: "/forget-password": { window: 300, max: 1, }, I can send as many 'forget password emails' as I want as long as I provide existing email I can throw custom error in hook:
if (ctx.path === "/forget-password") {
const validationResult = forgotPasswordSchema.safeParse(ctx.body);

if (!validationResult.success) {
console.log(validationResult.error.errors);
throw new APIError("BAD_REQUEST", {
message: validationResult.error.errors[0]?.message,
});
}

const dbUser = await db
.select()
.from(user)
.where(eq(user.email, ctx.body.email))
.then((r) => r[0]);

if (!dbUser) {
throw new APIError("NOT_FOUND", {
message: "Provided email is not registered",
});
}
}
if (ctx.path === "/forget-password") {
const validationResult = forgotPasswordSchema.safeParse(ctx.body);

if (!validationResult.success) {
console.log(validationResult.error.errors);
throw new APIError("BAD_REQUEST", {
message: validationResult.error.errors[0]?.message,
});
}

const dbUser = await db
.select()
.from(user)
.where(eq(user.email, ctx.body.email))
.then((r) => r[0]);

if (!dbUser) {
throw new APIError("NOT_FOUND", {
message: "Provided email is not registered",
});
}
}
And handle it on the client:
onError: async (ctx) => {
if (ctx.error.status === 429) {
toast(
<div className="flex flex-shrink-0 items-start gap-2 p-0">
<IconExclamationCircle className="h-full w-5 text-amber-500" />
<div className="flex flex-col gap-1">
<h6 className="text-sm">Too many Requests.</h6>
<p className="text-xs">
Please try again in couple minutes or contact support.
</p>
</div>
</div>,
{},
);
} else {
toast.error(ctx.error.message);
}
},
onError: async (ctx) => {
if (ctx.error.status === 429) {
toast(
<div className="flex flex-shrink-0 items-start gap-2 p-0">
<IconExclamationCircle className="h-full w-5 text-amber-500" />
<div className="flex flex-col gap-1">
<h6 className="text-sm">Too many Requests.</h6>
<p className="text-xs">
Please try again in couple minutes or contact support.
</p>
</div>
</div>,
{},
);
} else {
toast.error(ctx.error.message);
}
},
It displays the text of my error on the client in the toast only in local dev though . It is a bit strange as it returns 204 in my case in prod but triggers onError callback. Does anyone knows why ?

Did you find this page helpful?