Custom Password Reset in Laravel
Have you ever needed to customise Laravel’s password reset and add your own password reset flow? If so, keep on reading or bookmark for later reference.
I have been working with Laravel for over 2 years now and I always enjoy working with it because it makes my software development process easy, fast and reliable, because it ships with a lot of ready-made features. However, there are times when one needs to customise certain behaviour and then you discover Laravel has abstracted most operations really deep into the framework with a lot of traits, interfaces and base classes.
Although, Laravel provides hooks into certain actions but some times it’s just easier to customise things to get a clearer understanding of how the framework actually works. So if you need to know the inner workings of resetting a user’s password then, keep reading, else, go back to what you were doing before.
In case you continued reading, you must know that I assume you have a fair experience working with Laravel already. Also, I used this on Laravel 5.2 but it should work for the others, new and old.
Step 1 — Create a Route and a Controller
Create two routes, a controller and methods through which the email address that requires password reset will be submitted to via the password reset form. The name of these routes, controller and methods are totally up to you.
Route::post('reset_password_without_token', 'AccountsController@validatePasswordRequest');Route::post('reset_password_with_token', 'AccountsController@resetPassword');
Step 2— Change the action property on the default password reset form
<form method="POST" action="{{ url('/reset_password_without_token') }}">
The default password reset form can be found here: resources/views/auth/passwords/email.blade.php
Step 2 — Create token and Send Password Reset Link via email
Then add the validatePasswordRequest method in the AccountsController and use or modify the code below.
//You can add validation login here$user = DB::table('users')->where('email', '=', $request->email)
->first();//Check if the user exists
if (count($user) < 1) {
return redirect()->back()->withErrors(['email' => trans('User does not exist')]);
}
//Create Password Reset Token
DB::table('password_resets')->insert([
'email' => $request->email,
'token' => str_random(60),
'created_at' => Carbon::now()
]);//Get the token just created above
$tokenData = DB::table('password_resets')
->where('email', $request->email)->first();
if ($this->sendResetEmail($request->email, $tokenData->token)) {
return redirect()->back()->with('status', trans('A reset link has been sent to your email address.'));
} else {
return redirect()->back()->withErrors(['error' => trans('A Network Error occurred. Please try again.')]);
}
The sendResetEmail method is a private method that sends an email with the reset link to the user. Here you can use whatever email api you choose to use. Maybe, you have a custom email service for your organisation, you can use it here, not relying on the options Laravel has by default.
private function sendResetEmail($email, $token)
{//Retrieve the user from the database
$user = DB::table('users')->where('email', $email)->select('firstname', 'email')->first();//Generate, the password reset link. The token generated is embedded in the link$link = config('base_url') . 'password/reset/' . $token . '?email=' . urlencode($user->email);
try {
//Here send the link with CURL with an external email API return true;
} catch (\Exception $e) {
return false;
}
}
The link generated and sent to the user, when clicked will direct the user to you domain.com/password/reset/token?email=’user@email.com’. You can find the view here : resources/views/auth/passwords/reset.blade.php
From here, the user enters a new password twice, then we confirm the new passwords match, then we confirm the token matches the last one created.
Step 3 - Reset the User’s password
Add this method to the AccountsController. Go through the comments, they explain each step.
public function resetPassword(Request $request)
{
//Validate input
$validator = Validator::make($request->all(), [
'email' => 'required|email|exists:users,email',
'password' => 'required|confirmed'
'token' => 'required' ]);
//check if payload is valid before moving on
if ($validator->fails()) {
return redirect()->back()->withErrors(['email' => 'Please complete the form']);
}
$password = $request->password;// Validate the token
$tokenData = DB::table('password_resets')
->where('token', $request->token)->first();// Redirect the user back to the password reset request form if the token is invalid
if (!$tokenData) return view('auth.passwords.email');
$user = User::where('email', $tokenData->email)->first();
// Redirect the user back if the email is invalid
if (!$user) return redirect()->back()->withErrors(['email' => 'Email not found']);//Hash and update the new password
$user->password = \Hash::make($password);
$user->update(); //or $user->save();
//login the user immediately they change password successfully
Auth::login($user);
//Delete the token
DB::table('password_resets')->where('email', $user->email)
->delete();
//Send Email Reset Success Email
if ($this->sendSuccessEmail($tokenData->email)) {
return view('index');
} else {
return redirect()->back()->withErrors(['email' => trans('A Network Error occurred. Please try again.')]);
}
}
There you have it. This should be enough to get things working.