> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modelence.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Password Reset

Modelence provides a secure password reset flow with email tokens.

> The reset email delivery setup below (provider, sender, template, redirect URL) is configured under the **`email:`** key of `startApp`. To run code on related auth events, register callbacks under **`auth:`** — see [Auth Hooks](/authentication/hooks).

## Configuration

Configure password reset emails in your server setup:

```typescript theme={null}
import { startApp } from 'modelence/server';
import resendProvider from '@modelence/resend';

startApp({
  email: {
    provider: resendProvider,
    from: 'noreply@yourdomain.com',
    passwordReset: {
      subject: 'Reset your password',
      redirectUrl: 'https://yourdomain.com/reset-password',
    },
  },
});
```

`redirectUrl` is the page in your app where the user enters their new password. Modelence redirects here **after** validating the token and storing it in an `httpOnly` cookie, so this URL never carries the token. This is also where you call [`resetPassword({ password })`](#reset-password).

## How It Works

1. User requests a password reset by providing their email
2. A secure reset token is generated, hashed, and stored in the database (only the hash is stored, never the raw token)
3. An email with a reset link is sent to the user
4. The link points at a Modelence server route, **not** your SPA page. That route validates the token, stores it in a short-lived `httpOnly` cookie, and redirects to your configured `redirectUrl` **without the token in the URL**
5. On that page, the user enters their new password and you call `resetPassword({ password })` — the token is read server-side from the cookie, so it never reaches client JavaScript
6. Password is updated and the token is invalidated (single-use)
7. If the user's email was not yet verified, it is automatically marked as verified

Since the user must receive the reset email to complete the flow, a successful password reset proves ownership of the email address.

<Note>
  The reset token never reaches your client code or appears in a browser URL — it is exchanged server-side through an `httpOnly` cookie. This closes the window where a token could leak via the address bar, browser history, or the `Referer` header.
</Note>

<Note>
  The token cookie is sent with credentialed requests, so it works even when your SPA and Modelence API are on different origins. For that cross-origin case, the API must respond with `Access-Control-Allow-Credentials: true` and a specific (non-wildcard) allowed origin, otherwise the browser drops the cookie and the reset fails.
</Note>

### Base URL requirement

The reset email link is built from your site's base URL, so you must set it via the `_system.site.url` config (or the `MODELENCE_SITE_URL` environment variable). If it isn't set, sending a reset token fails with a clear error rather than emailing a broken link.

## Client Implementation

### Request Password Reset

```typescript theme={null}
import { sendResetPasswordToken } from 'modelence/client';

async function handleForgotPassword(email: string) {
  try {
    await sendResetPasswordToken({ email });
    console.log('Password reset email sent');
  } catch (error) {
    console.error('Error:', error.message);
  }
}
```

### Reset Password

The user lands on your `redirectUrl` page after clicking the email link. The token is already held in an `httpOnly` cookie, so you only collect the new password — don't pass a token:

```typescript theme={null}
import { resetPassword } from 'modelence/client';

async function handleResetPassword(newPassword: string) {
  try {
    await resetPassword({ password: newPassword });
    console.log('Password reset successful');
  } catch (error) {
    console.error('Reset failed:', error.message);
  }
}
```

<Warning>
  Passing a `token` to `resetPassword({ token, password })` is **deprecated**. It exists only for legacy flows that still carry the token client-side and will be removed. New apps should rely on the server-side cookie exchange and submit just the password.
</Warning>

## Custom Reset Email Template

```typescript theme={null}
startApp({
  email: {
    provider: resendProvider,
    from: 'noreply@yourdomain.com',
    passwordReset: {
      subject: 'Reset Your Password',
      template: ({ name, email, resetUrl }) => `
        <html>
          <body>
            <h1>Password Reset Request</h1>
            <p>Hi ${name || 'there'},</p>
            <p>We received a request to reset your password. Click the button below to proceed:</p>
            <a href="${resetUrl}"
               style="background-color: #5509D9; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
              Reset Password
            </a>
            <p>If you didn't request this, you can safely ignore this email.</p>
            <p>This link will expire in 1 hour.</p>
          </body>
        </html>
      `,
      redirectUrl: 'https://yourdomain.com/reset-password',
    },
  },
});
```
