Laravel Password Reset and Email Verification

Lesson 33 of 50 - Learn how users can reset forgotten passwords and verify their email addresses in Laravel.

66%
1. What Is Password Reset?

Password reset allows users to regain access to their accounts when they forget their passwords.

A typical password reset process is:

  1. The user opens the Forgot Password page.
  2. The user enters their registered email address.
  3. Laravel sends a password reset link by email.
  4. The user opens the link and enters a new password.
  5. Laravel validates the reset token and updates the password.
  6. The user can log in using the new password.

Example:

Registered Email:
student@example.com

Reset Link:
https://example.com/reset-password/TOKEN

New Password:
A new password chosen by the user

Laravel provides password reset services through its password broker. Your application still needs appropriate routes, forms, email configuration, and user model setup.

2. Configure Email Settings

Laravel sends password reset links and email verification messages through a configured mailer. For local development, use a mail testing service or another suitable development mail setup.

Example SMTP settings in .env:

MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="noreply@example.com"
MAIL_FROM_NAME="${APP_NAME}"

Replace the example host, username, password, and sender address with values supplied by your email provider.

Keep real email credentials private. Never commit production mail passwords to a public repository.

After changing environment configuration, clear cached configuration when necessary:

php artisan config:clear

Email delivery must be configured correctly for password reset and verification emails to reach users.

3. Prepare the User Model for Email Verification

Laravel's standard users table commonly includes an email_verified_at column. This column records when the user verified their email address.

If your users migration already contains this column, you do not need to add it again.

Example User model:

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable implements MustVerifyEmail
{
    use HasFactory, Notifiable;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }
}

Implementing MustVerifyEmail enables Laravel's standard email verification workflow when the appropriate routes and notifications are configured.

The hashed cast is available in modern Laravel versions. If your project uses an older version, use the casting and password hashing conventions supported by that version.

4. Create the Forgot Password Form

Create resources/views/auth/forgot-password.blade.php.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Forgot Password</title>
</head>
<body>

    <h2>Forgot Password</h2>

    @if (session('status'))
        <p style="color: green;">
            {{ session('status') }}
        </p>
    @endif

    @if ($errors->any())
        <ul style="color: red;">
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    @endif

    <form action="{{ route('password.email') }}" method="POST">
        @csrf

        <label for="email">Registered Email</label>

        <input
            type="email"
            id="email"
            name="email"
            value="{{ old('email') }}"
            required
        >

        <br><br>

        <button type="submit">Send Reset Link</button>
    </form>

</body>
</html>

The form submits the user's email address to the route named password.email.

That route must be configured to invoke Laravel's password broker or be supplied by your authentication starter kit.

5. Send a Password Reset Link

Laravel's Password facade provides access to the password reset broker.

Create a controller:

php artisan make:controller PasswordResetController

Open app/Http/Controllers/PasswordResetController.php.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;

class PasswordResetController extends Controller
{
    public function requestForm()
    {
        return view('auth.forgot-password');
    }

    public function sendResetLink(Request $request)
    {
        $request->validate([
            'email' => ['required', 'email'],
        ]);

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

        if ($status === Password::RESET_LINK_SENT) {
            return back()->with('status', __($status));
        }

        return back()
            ->withErrors(['email' => __($status)])
            ->onlyInput('email');
    }
}

Register the routes:

use App\Http\Controllers\PasswordResetController;
use Illuminate\Support\Facades\Route;

Route::middleware('guest')->group(function () {
    Route::get(
        '/forgot-password',
        [PasswordResetController::class, 'requestForm']
    )->name('password.request');

    Route::post(
        '/forgot-password',
        [PasswordResetController::class, 'sendResetLink']
    )->name('password.email');
});

Laravel generates a reset token and sends the reset notification through its configured mail system. The users table and password reset token storage must be configured correctly.

For public applications, avoid revealing whether a particular email address has an account. Use appropriate rate limiting and generic user-facing messages to reduce account enumeration risks.

6. Create the Reset Password Form

The reset email normally contains a URL with a password reset token. The user opens that link and enters a new password.

Create resources/views/auth/reset-password.blade.php.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Reset Password</title>
</head>
<body>

    <h2>Reset Your Password</h2>

    @if ($errors->any())
        <ul style="color: red;">
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    @endif

    <form action="{{ route('password.update') }}" method="POST">
        @csrf

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

        <label for="email">Email</label>
        <input
            type="email"
            id="email"
            name="email"
            value="{{ old('email', $email) }}"
            required
        >

        <br><br>

        <label for="password">New Password</label>
        <input
            type="password"
            id="password"
            name="password"
            required
        >

        <br><br>

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

        <br><br>

        <button type="submit">Reset Password</button>
    </form>

</body>
</html>

The $token and $email variables must be supplied by the controller when rendering the reset form.

7. Validate the Token and Save the New Password

The password broker validates the reset token, confirms the associated user, and updates the password when the reset is successful.

Add a method to PasswordResetController:

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;
use Illuminate\Validation\Rules\Password as PasswordRule;

public function resetPassword(Request $request)
{
    $validated = $request->validate([
        'token' => ['required'],
        'email' => ['required', 'email'],
        'password' => [
            'required',
            'confirmed',
            PasswordRule::min(8),
        ],
    ]);

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

            $user->save();

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

    if ($status === Password::PASSWORD_RESET) {
        return redirect()
            ->route('login')
            ->with('status', __($status));
    }

    return back()
        ->withErrors(['email' => __($status)]);
}

Register the reset form routes:

Route::middleware('guest')->group(function () {
    Route::get(
        '/reset-password/{token}',
        function (\Illuminate\Http\Request $request, string $token) {
            return view('auth.reset-password', [
                'token' => $token,
                'email' => $request->query('email', ''),
            ]);
        }
    )->name('password.reset');

    Route::post(
        '/reset-password',
        [PasswordResetController::class, 'resetPassword']
    )->name('password.update');
});

Add these routes inside the existing guest route group or merge them into it. Do not duplicate route names already supplied by an authentication starter kit.

The forceFill() call updates protected model attributes. The password broker checks the token and its validity before invoking the callback.

8. Email Verification

Email verification confirms that a user can access the email address used during registration.

Laravel supports email verification through the MustVerifyEmail interface and the verified middleware.

Protect a route with email verification:

use Illuminate\Support\Facades\Route;

Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth', 'verified'])
  ->name('dashboard');

Users must be authenticated and have a verified email address to access this route.

Display verification status in Blade:

@if (auth()->user()->hasVerifiedEmail())
    <p>Your email address is verified.</p>
@else
    <p>Please verify your email address.</p>
@endif

Manually verify an email in trusted code:

if (! $user->hasVerifiedEmail()) {
    $user->markEmailAsVerified();
}

Only perform manual verification after a trusted administrative action or another appropriate verification process. Do not let users verify arbitrary email addresses without proving ownership.

A complete verification workflow also needs a verification notification, signed verification URL, and appropriate verification routes. Authentication starter kits can provide these features.

9. Practical Assignment

Create a password reset and email verification system for your Laravel student management application.

  1. Configure an email mailer in your environment.
  2. Confirm that the users table has an email column.
  3. Confirm that password reset token storage is configured.
  4. Create a Forgot Password form.
  5. Validate the user's email address.
  6. Send a password reset link through Laravel's password broker.
  7. Create a Reset Password form.
  8. Validate the token and new password.
  9. Hash the new password before saving it.
  10. Implement email verification for new users.
  11. Protect the dashboard using auth and verified middleware.
  12. Test invalid, expired, and already-used reset links.

Challenge:

Add a resend verification email feature with rate limiting. Display a helpful message when a user requests a reset link and prevent unauthorized access to unverified accounts.

For production, use HTTPS, rate-limit sensitive endpoints, protect email credentials, and prefer Laravel's maintained authentication tooling where it fits your application.

Quick Quiz

1. Which Laravel facade sends a password reset link?




2. Which interface enables Laravel's standard email verification workflow?




3. Which middleware commonly requires a verified email address?