# Password Reset

> Self-service password recovery with secure email links. Users can reset forgotten passwords without admin intervention.

*Canonical: https://makerkit.dev/docs/tanstack-prisma/authentication/password-reset*

---

{% sequence title="Password Reset Flow" description="How users recover their password." %}

[User requests reset](#request-password-reset)

[Email with reset link is sent](#reset-email)

[User sets new password](#set-new-password)

[User is redirected to sign in](#after-reset)

{% /sequence %}

Users can reset forgotten passwords through a secure, self-service flow. The process uses time-limited email links to verify identity.

## Request Password Reset

**Route file:** `apps/web/src/routes/auth/password-reset.tsx`
**Route:** `/auth/password-reset`

{% img src="/images/docs/password-reset-request.webp" width="2388" height="1798" alt="Password reset request form showing email input field and send reset link button" /%}

Users access this page via the "Forgot your password?" link on the sign in page. They enter their email address to receive a reset link.

### Component

```tsx {% title="packages/auth/src/components/password-reset-request-container.tsx" %}
<PasswordResetRequestContainer
  redirectPath="/password-reset"
/>
```

### Security Considerations

- The form always shows a success message, even if the email doesn't exist (prevents email enumeration)
- Reset tokens are single-use and time-limited
- Previous reset tokens are invalidated when a new one is requested

## Reset Email

When a password reset is requested, Better Auth:

1. Generates a secure, random token
2. Stores the token with an expiration time
3. Sends an email with the reset link

### Email Template

Customize the reset email at:

```typescript {% title="packages/better-auth/src/emails/send-reset-password-email.ts" %}
export async function sendResetPasswordEmail({
  email,
  url,
  token,
  productName,
  language,
}: SendResetPasswordEmailOptions) {
  // Email sending logic
}
```

### Development Mode

In development, the reset link is logged to the console:

```bash
[info] Sending password reset email...
       email: user@example.com
       url: http://localhost:3000/password-reset?token=...
```

## Set New Password

**Route file:** `apps/web/src/routes/password-reset.tsx`
**Route:** `/password-reset`

{% img src="/images/docs/password-reset.webp" width="2388" height="1798" alt="New password form showing password and confirm password fields" /%}

When the user clicks the link in their email, they're taken to a form to enter their new password. The route reads the `token` from the URL search params and renders `PasswordResetForm`; its `beforeLoad` redirects to `/auth/password-reset` when the token is missing.

### Token Validation

The token in the URL is validated:

1. Token exists and hasn't been used
2. Token hasn't expired
3. User associated with token exists

If validation fails, the user sees an error message and can request a new reset link.

### Password Requirements

The new password must meet the same requirements as registration:

| Requirement | Value |
|-------------|-------|
| Minimum length | 8 characters |
| Maximum length | 99 characters |

## After Reset

After successfully setting a new password:

1. The reset token is invalidated
2. All existing sessions for the user are optionally revoked
3. User is redirected to the sign in page
4. User signs in with their new password

## Programmatic Password Reset

For custom flows, use the auth client:

### Request Reset

```typescript
'use client';

import { authClient } from '@kit/better-auth/client';

async function requestPasswordReset(email: string) {
  const result = await authClient.requestPasswordReset({
    email,
    redirectTo: '/password-reset',
  });

  if (result.error) {
    console.error(result.error.message);
    return;
  }

  // Show success message
}
```

### Complete Reset

```typescript
'use client';

import { authClient } from '@kit/better-auth/client';

async function resetPassword(token: string, newPassword: string) {
  const result = await authClient.resetPassword({
    token,
    newPassword,
  });

  if (result.error) {
    console.error(result.error.message);
    return;
  }

  // Redirect to sign in
  window.location.href = '/auth/sign-in';
}
```

## Configuration

Password reset is enabled automatically when email/password authentication is enabled:

```typescript {% title="packages/better-auth/src/auth.ts" %}
emailAndPassword: {
  enabled: process.env.VITE_AUTH_PASSWORD === 'true',
  requireEmailVerification: true,
  sendResetPassword: sendResetPasswordEmail,  // Email handler
},
```

### Token Expiration

By default, reset tokens expire after 1 hour. This is configured in Better Auth's settings.

## Common Issues

### "Invalid or expired token" Error

1. Token has already been used (single-use)
2. Token has expired (request a new one)
3. URL was truncated or modified

### Reset Email Not Received

1. Check spam/junk folder
2. Verify mailer configuration
3. Ensure the email exists in the database
4. In development, check the console for logged URLs

### User Still Can't Sign In After Reset

1. Make sure they're using the new password
2. Check if email verification is still pending
3. Verify the account isn't locked or banned

---

**Previous:** [Sign Up ←](./sign-up) | **Next:** [Session Handling →](./session-handling)
