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

# Restablecimiento de contraseña

> Cómo implementar el restablecimiento de contraseña en Laravel

## Introducción

El restablecimiento de contraseña es un flujo de autenticación imprescindible en las aplicaciones web. Los starter kits lo construyen automáticamente; con proyectos solo de API o con UI propia, hay que implementarlo manualmente.

**Cuándo hace falta implementarlo manualmente:**

* Backend solo de API (frontend SPA o app móvil)
* Cuando construyes la UI de autenticación sin usar un starter kit
* Cuando quieres personalizar totalmente el correo o el diseño de la URL

<Info>
  Si creaste el proyecto con un starter kit (`laravel new`), la funcionalidad ya está implementada. Esta página explica cómo hacerlo sin starter kit.
</Info>

Flujo general:

```mermaid theme={null}
flowchart TD
    A["Usuario"] --> B["Introduce el correo<br>GET /forgot-password"]
    B --> C["Envío del enlace<br>POST /forgot-password"]
    C --> D["Recibe el correo<br>URL con token"]
    D --> E["Muestra el formulario<br>GET /reset-password/{token}"]
    E --> F["Cambia la contraseña<br>POST /reset-password"]
    F --> G["Redirige al<br>login"]
```

## Configuración

La configuración vive en la clave `passwords` de `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` — Almacenamiento (`database` o `cache`)
* `expire` — Validez del token en minutos. 60 por defecto
* `throttle` — Tiempo (segundos) hasta permitir un reenvío

## Drivers

### Driver `database`

Es el driver por defecto. Guarda los tokens en la tabla `password_reset_tokens`. Esa tabla la incluye la migración por defecto (`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,
    ],
],
```

### Driver `cache`

<Tip>
  Es una opción disponible desde Laravel 11. No necesita la tabla, por lo que la configuración es más sencilla.
</Tip>

El driver `cache` guarda los tokens en el almacén de caché. Como se usa el correo como clave, evita usar el correo del usuario como clave de caché en otras partes de la app.

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

Indica un store dedicado con `store` para que `php artisan cache:clear` no borre los tokens. El valor corresponde a un store definido en `config/cache.php`.

## Preparación del modelo

Para usar el restablecimiento, el modelo `App\Models\User` necesita dos 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` — Necesario para enviar notificaciones por correo
* `CanResetPassword` — Aporta métodos para generar y validar el token de restablecimiento

<Info>
  El modelo `User` por defecto de Laravel ya incluye estos traits. En una instalación nueva no hay que añadir nada.
</Info>

## Implementación de las rutas

Se necesitan cuatro rutas.

### 1. Formulario para solicitar el enlace

Muestra el formulario para introducir el correo.

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

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

Vista Blade correspondiente:

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

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

    <div>
        <label for="email">Correo electrónico</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">Enviar enlace de restablecimiento</button>
</form>
```

### 2. Envío del enlace

Recibe el formulario y envía el correo con `Password::sendResetLink()`.

```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()` devuelve un string con el estado.

| Constante                   | Descripción                      |
| --------------------------- | -------------------------------- |
| `Password::ResetLinkSent`   | Enlace enviado                   |
| `Password::INVALID_USER`    | No existe usuario con ese correo |
| `Password::RESET_THROTTLED` | Envío limitado por throttling    |

### 3. Formulario de restablecimiento

Al pulsar el enlace del correo se muestra el formulario para introducir la nueva contraseña.

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

Vista Blade:

```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">Correo electrónico</label>
        <input id="email" type="email" name="email" value="{{ old('email') }}" required autofocus>
        @error('email')
            <span>{{ $message }}</span>
        @enderror
    </div>

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

    <div>
        <label for="password_confirmation">Confirmar contraseña</label>
        <input id="password_confirmation" type="password" name="password_confirmation" required>
    </div>

    <button type="submit">Restablecer contraseña</button>
</form>
```

### 4. Ejecución del restablecimiento

Recibe el formulario y actualiza la contraseña con `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');
```

Constantes de estado de `Password::reset()`:

| Constante                 | Descripción                      |
| ------------------------- | -------------------------------- |
| `Password::PasswordReset` | Restablecimiento correcto        |
| `Password::INVALID_TOKEN` | Token no válido o caducado       |
| `Password::INVALID_USER`  | No existe usuario con ese correo |

## Vida útil del token

En `expire` de `config/auth.php` configuras la validez del token en minutos. Por defecto son 60.

```php theme={null}
'passwords' => [
    'users' => [
        'driver' => 'database',
        'expire' => 60, // caduca a los 60 min
        'throttle' => 60,
    ],
],
```

Con el driver `database`, los tokens caducados permanecen en la BD. Límpialos periódicamente con:

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

Se recomienda automatizarlo con el scheduler.

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

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

## Personalización

### Notificación personalizada

Para personalizar el correo, sobreescribe `sendPasswordResetNotification` en el modelo `User`.

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

class User extends Authenticatable
{
    use Notifiable, CanResetPassword;

    /**
     * Envía la notificación de restablecimiento
     */
    public function sendPasswordResetNotification($token): void
    {
        $url = 'https://example.com/reset-password?token='.$token;

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

### Personalizar la URL del enlace

En el `boot` de `AppServiceProvider`, usa `ResetPassword::createUrlUsing()` para cambiar la URL del enlace. Útil si tu frontend (SPA) está en otro origen.

```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;
    });
}
```

### Trusted Hosts

Los enlaces se generan a partir de la cabecera `Host` de la solicitud HTTP. Para evitar solicitudes con hosts falsos, configura Trusted Hosts en `bootstrap/app.php`.

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

<Warning>
  Al implementar el restablecimiento, comprueba siempre la configuración de Trusted Hosts. Sin ella hay riesgo de ataques de host header injection.
</Warning>

## Resumen

| Objetivo                      | Método                                         |
| ----------------------------- | ---------------------------------------------- |
| Enviar el enlace              | `Password::sendResetLink(['email' => $email])` |
| Restablecer la contraseña     | `Password::reset($credentials, $callback)`     |
| Restablecer sin usar la tabla | Configurar el driver `cache`                   |
| Borrar tokens caducados       | `php artisan auth:clear-resets`                |
| Personalizar el correo        | Sobreescribir `sendPasswordResetNotification`  |
| Personalizar la URL           | `ResetPassword::createUrlUsing()`              |

## Próximos pasos

<Columns cols={2}>
  <Card title="Introducción a la autenticación" icon="lock" href="/es/authentication">
    Aprende el sistema de autenticación completo de Laravel.
  </Card>

  <Card title="Notificaciones" icon="bell" href="/es/notifications">
    Personalización de notificaciones por correo.
  </Card>
</Columns>


## Related topics

- [Actualización de Laravel 10 a 11](/es/blog/upgrade-10-to-11.md)
- [Actualización de Laravel 8 a 9](/es/blog/upgrade-8-to-9.md)
- [Hashing (hasheo)](/es/hashing.md)
- [Laravel Prompts](/es/prompts.md)
- [Guía de migración de laravel/ui a Fortify](/es/blog/ui-to-fortify.md)
