Password Reset¶
The password reset flow is handled entirely by Better-auth's built-in email verification system, with Nodemailer sending emails through Mailpit (dev) or SMTP (production).
Flow¶
sequenceDiagram
participant User
participant Frontend
participant Hono as Hono API
participant Email
User->>Frontend: Enter email on "Forgot Password" screen
Frontend->>Hono: POST /api/auth/forget-password { email }
Hono->>Hono: Generate reset token, save to DB
Hono->>Email: Send reset email with link
Hono-->>Frontend: 200 OK
User->>Frontend: Click link in email → Reset Password screen
Frontend->>Hono: POST /api/auth/reset-password { token, newPassword }
Hono->>Hono: Validate token, update password (Argon2), invalidate token
Hono-->>Frontend: 200 OK
Frontend->>Frontend: Redirect to sign-in
API Endpoints¶
Request Reset¶
Always returns 200 OK regardless of whether the email exists (prevents enumeration).
Confirm Reset¶
POST /api/auth/reset-password
Content-Type: application/json
{
"token": "<token-from-email-link>",
"newPassword": "NewPassword123!"
}
Returns 200 OK on success, 400 if the token is invalid or expired.
Email Configuration¶
Development (Mailpit)¶
Open Mailpit at http://localhost:8025 to inspect sent emails.
Production (SMTP)¶
EMAIL_FROM="Coaching App <noreply@yourdomain.com>"
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASS=<sendgrid-api-key>
Email Template¶
Better-auth calls a custom sendEmail function configured in
backend/src/lib/auth.ts. The template lives in backend/templates/:
// backend/src/lib/auth.ts
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
sendResetPassword: async ({ user, url }) => {
await sendEmail({
to: user.email,
subject: 'Reset your password',
html: renderTemplate('password-reset', {
name: user.name,
resetUrl: url,
expiresIn: '1 hour',
}),
});
},
},
});
Reset Link URL¶
The reset link is constructed from BETTER_AUTH_URL + /reset-password?token=<token>.
The frontend handles this route in frontend/src/screens/auth/ResetPasswordScreen.tsx.
Security¶
- Reset tokens expire after 1 hour by default.
- Each token is single-use — it's invalidated immediately after use.
- Always returns
200 OKfor the request step (prevents email enumeration). - Rate limiting is handled at the Cloudflare level; see Cloudflare Setup.