Saltar al contenido principal

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
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.
Flujo general:

Configuración

La configuración vive en la clave passwords de config/auth.php.
// 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).
'passwords' => [
    'users' => [
        'driver' => 'database',
        'provider' => 'users',
        'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
        'expire' => 60,
        'throttle' => 60,
    ],
],

Driver cache

Es una opción disponible desde Laravel 11. No necesita la tabla, por lo que la configuración es más sencilla.
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.
'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

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
El modelo User por defecto de Laravel ya incluye estos traits. En una instalación nueva no hay que añadir nada.

Implementación de las rutas

Se necesitan cuatro rutas.

1. Formulario para solicitar el enlace

Muestra el formulario para introducir el correo.
// routes/web.php

Route::get('/forgot-password', function () {
    return view('auth.forgot-password');
})->middleware('guest')->name('password.request');
Vista Blade correspondiente:
{{-- 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().
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.
ConstanteDescripción
Password::ResetLinkSentEnlace enviado
Password::INVALID_USERNo existe usuario con ese correo
Password::RESET_THROTTLEDEnvío limitado por throttling

3. Formulario de restablecimiento

Al pulsar el enlace del correo se muestra el formulario para introducir la nueva contraseña.
Route::get('/reset-password/{token}', function (string $token) {
    return view('auth.reset-password', ['token' => $token]);
})->middleware('guest')->name('password.reset');
Vista Blade:
{{-- 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().
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():
ConstanteDescripción
Password::PasswordResetRestablecimiento correcto
Password::INVALID_TOKENToken no válido o caducado
Password::INVALID_USERNo 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.
'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:
php artisan auth:clear-resets
Se recomienda automatizarlo con el scheduler.
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.
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.
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.
->withMiddleware(function (Middleware $middleware) {
    $middleware->trustHosts(at: ['example.com']);
})
Al implementar el restablecimiento, comprueba siempre la configuración de Trusted Hosts. Sin ella hay riesgo de ataques de host header injection.

Resumen

ObjetivoMétodo
Enviar el enlacePassword::sendResetLink(['email' => $email])
Restablecer la contraseñaPassword::reset($credentials, $callback)
Restablecer sin usar la tablaConfigurar el driver cache
Borrar tokens caducadosphp artisan auth:clear-resets
Personalizar el correoSobreescribir sendPasswordResetNotification
Personalizar la URLResetPassword::createUrlUsing()

Próximos pasos

Introducción a la autenticación

Aprende el sistema de autenticación completo de Laravel.

Notificaciones

Personalización de notificaciones por correo.
Última modificación el 13 de julio de 2026