Lesson 33 of 50 - Learn how users can reset forgotten passwords and verify their email addresses in Laravel.
Password reset allows users to regain access to their accounts when they forget their passwords.
A typical password reset process is:
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.
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.
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.
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.
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.
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.
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.
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.
Create a password reset and email verification system for your Laravel student management application.
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.
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?