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

# Password reset

> How to implement a password reset feature in Laravel

## Introduction

Password reset is an essential authentication flow for web applications. If you use a starter kit, this feature is built for you automatically, but for API-only projects and projects with a custom UI, you'll need to implement it manually.

**When manual implementation is required:**

* API-only backends (frontend is an SPA or mobile app)
* Building your own authentication UI without using a starter kit
* When you want to fully customize the mail notification and reset URL design

<Info>
  If you created your project using a starter kit (`laravel new`), password reset is already implemented. This page describes how to implement it without a starter kit.
</Info>

The full password reset flow looks like this:

```mermaid theme={null}
flowchart TD
    A["User"] --> B["Enter email<br>GET /forgot-password"]
    B --> C["Send reset link<br>POST /forgot-password"]
    C --> D["Receive email<br>URL with token"]
    D --> E["Show reset form<br>GET /reset-password/{token}"]
    E --> F["Change password<br>POST /reset-password"]
    F --> G["Redirect to<br>login screen"]
```

## Configuration

Password reset configuration is managed under the `passwords` key in `config/auth.php`.

```php theme={null}
// config/auth.php

'passwords' => [
    'users' => [
        'driver' => 'database',
        'provider' => 'users',
        'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
        'expire' => 60,
        'throttle' => 60,
    ],
],
```

* `driver` — how data is stored (`database` or `cache`)
* `expire` — token expiration (in minutes). Default is 60 minutes
* `throttle` — wait time (in seconds) before another send is allowed

## Drivers

### The database driver

The default driver. It stores password reset tokens in the database `password_reset_tokens` table. This table is included in Laravel's default migration (`0001_01_01_000000_create_users_table.php`).

```php theme={null}
'passwords' => [
    'users' => [
        'driver' => 'database',
        'provider' => 'users',
        'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
        'expire' => 60,
        'throttle' => 60,
    ],
],
```

### The cache driver

<Tip>
  A newer option available since Laravel 11. Since it doesn't require a database table, you can implement password reset in a simpler setup.
</Tip>

The `cache` driver stores tokens in the cache store. It removes the need for the `password_reset_tokens` migration. Tokens are stored keyed by the user's email address, so avoid using the email address as a cache key elsewhere in your app.

```php theme={null}
'passwords' => [
    'users' => [
        'driver' => 'cache',
        'provider' => 'users',
        'store' => 'passwords', // Optional: dedicated cache store
        'expire' => 60,
        'throttle' => 60,
    ],
],
```

Specifying a dedicated cache store under the `store` key prevents reset data from being erased by `php artisan cache:clear`. The value should match a store name configured in `config/cache.php`.

## Preparing your model

To use password reset, the `App\Models\User` model needs two traits.

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable, CanResetPassword;

    // ...
}
```

* `Notifiable` — required to send mail notifications
* `CanResetPassword` — provides methods needed for generating and verifying password reset tokens

<Info>
  Laravel's default `User` model already includes these traits. On a fresh install you don't need to add them.
</Info>

## Implementing routes

Password reset requires four routes.

### 1. The reset link request form

Displays a form for the user to enter their email address.

```php theme={null}
// routes/web.php

Route::get('/forgot-password', function () {
    return view('auth.forgot-password');
})->middleware('guest')->name('password.request');
```

The corresponding Blade view:

```blade theme={null}
{{-- resources/views/auth/forgot-password.blade.php --}}

<form method="POST" action="/forgot-password">
    @csrf

    <div>
        <label for="email">Email</label>
        <input id="email" type="email" name="email" value="{{ old('email') }}" required autofocus>
        @error('email')
            <span>{{ $message }}</span>
        @enderror
    </div>

    @if (session('status'))
        <div>{{ session('status') }}</div>
    @endif

    <button type="submit">Send reset link</button>
</form>
```

### 2. Handling the reset link send

Receives the form submission and uses `Password::sendResetLink()` to send the reset email.

```php theme={null}
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;

Route::post('/forgot-password', function (Request $request) {
    $request->validate(['email' => 'required|email']);

    $status = Password::sendResetLink(
        $request->only('email')
    );

    return $status === Password::ResetLinkSent
        ? back()->with(['status' => __($status)])
        : back()->withErrors(['email' => __($status)]);
})->middleware('guest')->name('password.email');
```

`Password::sendResetLink()` returns a status string.

| Status constant             | Description                           |
| --------------------------- | ------------------------------------- |
| `Password::ResetLinkSent`   | The reset link was sent               |
| `Password::INVALID_USER`    | No user was found for the given email |
| `Password::RESET_THROTTLED` | Sending is being throttled            |

### 3. The password reset form

Displays a form for the user to enter a new password after clicking the link in the email.

```php theme={null}
Route::get('/reset-password/{token}', function (string $token) {
    return view('auth.reset-password', ['token' => $token]);
})->middleware('guest')->name('password.reset');
```

The corresponding Blade view:

```blade theme={null}
{{-- resources/views/auth/reset-password.blade.php --}}

<form method="POST" action="/reset-password">
    @csrf

    <input type="hidden" name="token" value="{{ $token }}">

    <div>
        <label for="email">Email</label>
        <input id="email" type="email" name="email" value="{{ old('email') }}" required autofocus>
        @error('email')
            <span>{{ $message }}</span>
        @enderror
    </div>

    <div>
        <label for="password">New password</label>
        <input id="password" type="password" name="password" required>
        @error('password')
            <span>{{ $message }}</span>
        @enderror
    </div>

    <div>
        <label for="password_confirmation">Confirm password</label>
        <input id="password_confirmation" type="password" name="password_confirmation" required>
    </div>

    <button type="submit">Reset password</button>
</form>
```

### 4. Executing the password reset

Receives the form and updates the password with `Password::reset()`.

```php theme={null}
use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;

Route::post('/reset-password', function (Request $request) {
    $request->validate([
        'token' => 'required',
        'email' => 'required|email',
        'password' => 'required|min:8|confirmed',
    ]);

    $status = Password::reset(
        $request->only('email', 'password', 'password_confirmation', 'token'),
        function (User $user, string $password) {
            $user->forceFill([
                'password' => Hash::make($password),
            ])->setRememberToken(Str::random(60));

            $user->save();

            event(new PasswordReset($user));
        }
    );

    return $status === Password::PasswordReset
        ? redirect()->route('login')->with('status', __($status))
        : back()->withErrors(['email' => [__($status)]]);
})->middleware('guest')->name('password.update');
```

Status constants for `Password::reset()`:

| Status constant           | Description                           |
| ------------------------- | ------------------------------------- |
| `Password::PasswordReset` | Password reset succeeded              |
| `Password::INVALID_TOKEN` | Token is invalid or expired           |
| `Password::INVALID_USER`  | No user was found for the given email |

## Token expiration

You can set the token expiration in minutes via the `expire` option in `config/auth.php`. The default is 60 minutes.

```php theme={null}
'passwords' => [
    'users' => [
        'driver' => 'database',
        'expire' => 60, // Expires after 60 minutes
        'throttle' => 60,
    ],
],
```

When using the `database` driver, expired tokens remain in the database. To clean them up periodically, use the following Artisan command.

```shell theme={null}
php artisan auth:clear-resets
```

We recommend automating this via the scheduler.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('auth:clear-resets')->everyFifteenMinutes();
```

## Customization

### Using a custom notification

To customize the password reset email, override the `sendPasswordResetNotification` method on your `User` model.

```php theme={null}
use App\Notifications\ResetPasswordNotification;

class User extends Authenticatable
{
    use Notifiable, CanResetPassword;

    /**
     * Send the password reset notification.
     */
    public function sendPasswordResetNotification($token): void
    {
        $url = 'https://example.com/reset-password?token='.$token;

        $this->notify(new ResetPasswordNotification($url));
    }
}
```

### Customizing the reset link URL

Use `ResetPassword::createUrlUsing()` in your `AppServiceProvider`'s `boot` method to change the reset link URL. This is useful when redirecting to a frontend on a different origin, such as an SPA.

```php theme={null}
use App\Models\User;
use Illuminate\Auth\Notifications\ResetPassword;

public function boot(): void
{
    ResetPassword::createUrlUsing(function (User $user, string $token) {
        return 'https://example.com/reset-password?token='.$token;
    });
}
```

### Configuring Trusted Hosts

Password reset links are generated using the `Host` header of the HTTP request. To prevent requests from unauthorized hosts, we recommend configuring Trusted Hosts in `bootstrap/app.php`.

```php theme={null}
->withMiddleware(function (Middleware $middleware) {
    $middleware->trustHosts(at: ['example.com']);
})
```

<Warning>
  When implementing password reset, always review your Trusted Hosts configuration. Insufficient configuration risks host header injection attacks.
</Warning>

## Summary

| What you want to do              | How                                            |
| -------------------------------- | ---------------------------------------------- |
| Send a reset link                | `Password::sendResetLink(['email' => $email])` |
| Reset the password               | `Password::reset($credentials, $callback)`     |
| Reset without a table            | Configure the `cache` driver                   |
| Delete expired tokens            | `php artisan auth:clear-resets`                |
| Customize the email notification | Override `sendPasswordResetNotification`       |
| Customize the reset URL          | `ResetPassword::createUrlUsing()`              |

## Next steps

<Columns cols={2}>
  <Card title="Authentication introduction" icon="lock" href="/en/authentication">
    Learn the overall architecture of Laravel's authentication system.
  </Card>

  <Card title="Notifications" icon="bell" href="/en/notifications">
    Learn about customizing mail notifications in detail.
  </Card>
</Columns>


## Related topics

- [Laravel Fortify and Starter Kits](/en/advanced/fortify.md)
- [Migration guide from laravel/ui to Fortify](/en/blog/ui-to-fortify.md)
- [Authentication introduction](/en/authentication.md)
- [Starter kits](/en/starter-kits.md)
- [Build SPAs with Inertia.js](/en/blog/inertia-introduction.md)
