Whenever you create login and signup functionality for user authentication, you require a password reset option. This password reset link will help when the users forgot their password. In the reset link, there will be generally a token and that token needs to validate. In Laravel, there are default auth scaffolding like UI auth, Breeze Auth, Jetstream, and many more. These auth packages provide the inbuilt functionalities for login, signup, and password reset. But, when you are going to create an application without these auth packages. Then you will need to handle the password reset link manually. Today, in this post, I am going to cover the forgot password functionality with a password reset link through email. So, let’s start it.
Prerequisites
Here, I will be starting by creating a new project in Laravel 8. But, before creating any project in Laravel 8, you will need to have to follow configurations-
- PHP >= 7.3
- MySQL (version > 5)
- Apache/Nginx Server
- Composer
I am assuming, you are ready to create a new project. So, let’s start with the composer.
Create a Project in Laravel 8 For Password Reset
For creating this project, I will be using the composer. So, you need to open the terminal or command prompt and hit the below command.
composer create-project --prefer-dist laravel/laravel password-reset
The above command will start creating a new project in Laravel 8. So, you will have to wait while it completes.
After creating the project, you will need to have a database configuration. So, firstly, create a database inside MySQL.
Pass Data to Master Layout By Service Provider in Laravel 8
Create and Configure Database
For creating the database, I am using the MySQL command line. So, the below command will create the database for it.
CREATE DATABASE password_reset;
After creating the database, let’s open the project inside the VS code editor. Now, configure the database credentials for it.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=password_reset
DB_USERNAME=root
DB_PASSWORD=root
Here, I have configured the database credentials in the project.
In the next step, we will move for creating the model and migration for implementing the password reset functionality.
Redirect HTTP to HTTPS Using Middleware in Laravel 8
Create Model and Migration For Reset Password
In this project, I am not going to implement the Login and Sign Up functionality. I already posted the tutorial for it. So, I am skipping login and sign up. I will just create the password reset functionality. For the modal and migration, Laravel provides a default for the user. So, let’s use this one by adding more fields inside the migration file.
Firstly, navigate to the database/migrations folder, and you will have the users table migration file. Simply open it, and then add some more fields as showing below.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('token')->nullable();
$table->tinyInteger('is_verified')->default(0);
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
After adding the fields in the users migration file, you will need to migrate it.
php artisan migrate
The above command will migrate the tables as showing below.
Create REST API in Laravel 8 Using JWT Authentication
Add Fillable Data For User Model
After the migration of tables, you will need to add the fillable data in the User.php (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;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
'token',
'is_verified'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
So, we added the fillable data in the User.php file. Now, you will need to create a MailableClass for sending a password reset email link.
How to Create Github Login in Laravel 8 Using Socialite
Create Mail Class in Laravel For Reset Password
Laravel provides the Mail class for sending emails. You will need to configure the email settings for this. You can create a mail class using the artisan command.
php artisan make:mail ResetPassword
The above command will create a mail class inside the app/Mail folder. Now, put the code inside it.
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class ResetPassword extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* @return void
*/
public $name;
public $token;
public function __construct($name, $token)
{
$this->name = $name;
$this->token = $token;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
$user['name'] = $this->name;
$user['token'] = $this->token;
return $this->from("yoursenderemail@mail.com", "Sender Name")
->subject('Password Reset Link')
->view('template.reset-password', ['user' => $user]);
}
}
In the above code, I have received two parameters that are name and token. The name and token will be coming from the users table. We will see that one. Here, I have passed an email template for token, name, and a button for password reset.
So, let’s design a basic email template for this.
How to Create Login with Twitter in Laravel 8 Using Socialite
Create an Email Template For Password Reset
For creating an email template, just create a folder inside the resources/views named template. Then create a new blade file inside the folder. The template blade name will be reset-password.blade.php.
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="x-apple-disable-message-reformatting">
<title></title>
<style>
table,
td,
div,
h1,
p {
font-weight: 500;
font-family: Arial, sans-serif;
}
.btn {margin: 10px 0px;
border-radius: 4px;
text-decoration: none;
color: #fff !important;
height: 46px;
padding: 10px 20px;
font-size: 16px;
font-weight: 600;
background-image: linear-gradient(to right top, #021d68, #052579, #072d8b, #09369d, #093fb0) !important;
}
.btn:hover {
text-decoration: none;
opacity: .8;
}
</style>
</head>
<body style="margin:0;padding:0;">
<table role="presentation"
style="width:100%;border-collapse:collapse;border:0;border-spacing:0;background:#ffffff;">
<tr>
<td align="center" style="padding:0;">
<table role="presentation"
style="width:600px;border-collapse:collapse;border:1px solid #cccccc;border-spacing:0;text-align:left;">
<tr style="border-collapse:collapse;border:1px solid #cccccc;border-spacing:0;">
<td align="left" style="padding:10px 25px;background:#fff; display: flex; align-items: center;">
<span style="font-weight: bold; padding-top: 10px;"> Programming Fields </span>
</td>
</tr>
<tr>
<td style="padding:36px 30px 42px 30px;">
<table role="presentation"
style="width:100%;border-collapse:collapse;border:0;border-spacing:0;">
<tr>
<td style="padding:0 0 36px 0;color:#153643;">
<p style="font-weight:bold;margin:0 0 20px 0;font-family:Arial,sans-serif;">
Hello {{ $user ? $user['name'] : '' }},</h1>
<p
style="margin:0 0 12px 0;font-size:14px;line-height:24px;font-family:Arial,sans-serif;">
We've received a request to reset the password.
</p>
<p
style="margin:10px 0 12px 0;font-size:14px;line-height:24px;font-family:Arial,sans-serif;">
You can reset your password by clicking the button below:
</p>
<p style="text-align: center;">
<a href="{{'http://localhost:8000/forgot-password/'.$user['token']}}" class="btn">Reset your password</a>
</p>
<p style="margin:100px 0 12px 0;font-size:14px;font-family:Arial,sans-serif;">
Thank
you, </p>
<p style="margin:0 0 12px 0;font-size:14px;font-family:Arial,sans-serif;">
Programming Fields </p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
Now, come to the functionality part. Hence, let’s create a controller for implementing the reset password functionality.
Create LinkedIn Login in Laravel 8 Using Socialite
Create Controller For Reset Password
For resetting the password for the user, you will require a controller first. Hence, hit the below command to create a controller.
php artisan make:controller UserController
After creating the controller, let’s put the functionality for it.
<?php
namespace App\Http\Controllers;
use App\Mail\ResetPassword;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
class UserController extends Controller
{
/**
* Register user
* @param request
* @return response
*/
public function register(Request $request)
{
$this->validate($request, [
'name' => 'required',
'email' => 'required|email',
'password' => 'required|min:6'
]);
User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);
}
/**
* Login page
* @param NA
* @return view
*/
public function login()
{
return view('auth.login');
}
/**
* User Login
* @param request
* @return response
*/
public function loginValidate(Request $request)
{
$this->validate($request, [
'email' => 'required|email',
'password' => 'required|min:6'
]);
$user = User::where('email', $request->email)->first();
if ($user) {
if (Hash::check($request->password, $user->password)) {
return back()->with('success', 'Success! You are logged in');
}
return back()->with('failed', 'Failed! Invalid password');
}
return back()->with('failed', 'Failed! Invalid email');
}
/**
* Forgot password
* @param NA
* @return view
*/
public function forgotPassword()
{
return view('auth.forgot-password');
}
/**
* Validate token for forgot password
* @param token
* @return view
*/
public function forgotPasswordValidate($token)
{
$user = User::where('token', $token)->where('is_verified', 0)->first();
if ($user) {
$email = $user->email;
return view('auth.change-password', compact('email'));
}
return redirect()->route('forgot-password')->with('failed', 'Password reset link is expired');
}
/**
* Reset password
* @param request
* @return response
*/
public function resetPassword(Request $request)
{
$this->validate($request, [
'email' => 'required|email',
]);
$user = User::where('email', $request->email)->first();
if (!$user) {
return back()->with('failed', 'Failed! email is not registered.');
}
$token = Str::random(60);
$user['token'] = $token;
$user['is_verified'] = 0;
$user->save();
Mail::to($request->email)->send(new ResetPassword($user->name, $token));
if(Mail::failures() != 0) {
return back()->with('success', 'Success! password reset link has been sent to your email');
}
return back()->with('failed', 'Failed! there is some issue with email provider');
}
/**
* Change password
* @param request
* @return response
*/
public function updatePassword(Request $request) {
$this->validate($request, [
'email' => 'required',
'password' => 'required|min:6',
'confirm_password' => 'required|same:password'
]);
$user = User::where('email', $request->email)->first();
if ($user) {
$user['is_verified'] = 0;
$user['token'] = '';
$user['password'] = Hash::make($request->password);
$user->save();
return redirect()->route('login')->with('success', 'Success! password has been changed');
}
return redirect()->route('forgot-password')->with('failed', 'Failed! something went wrong');
}
}
Create Socialite Login with Google Account in Laravel 8
Add Routes For Reset Password
For managing the functionalities, you will require to have the routes. So, add the below routes inside the web.php file.
Route::get('register', [UserController::class, 'register'])->name('register');
Route::get('login', [UserController::class, 'login'])->name('login');
Route::post('login', [UserController::class, 'loginValidate'])->name('login');
Route::get('forgot-password', [UserController::class, 'forgotPassword'])->name('forgot-password');
Route::get('forgot-password/{token}', [UserController::class, 'forgotPasswordValidate']);
Route::post('forgot-password', [UserController::class, 'resetPassword'])->name('forgot-password');
Route::put('reset-password', [UserController::class, 'updatePassword'])->name('reset-password');
Next, you will need to configure the email settings.
Email Configuration in Laravel 8
After adding the functionality, you will need to configure the email settings. You can use any email provider. In my case, I have configured Gmail SMTP for sending password reset link. So, open the .env file and do the needful setup for the email configuration.
MAIL_MAILER=smtp
MAIL_HOST=SMTP_HOST
MAIL_PORT=SMTP_PORT
MAIL_USERNAME=YOUR_MAIL_ADDRESS
MAIL_PASSWORD=YOUR_PASSWORD
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
After that, you will require some views for performing login, reset password, and change password options.
Create Views
You will need to create the following blade file inside the views folder. Firstly, create a folder named auth inside the views folder. Now, start creating the blade file as showing below.
- login.blade.php
- forgot-password.blade.php
- change-password.blade.php
After creating the views, let’s put the code inside it one by one.
Login Blade File
<!doctype html>
<html lang="en">
<head>
<title>Programming Fields | Login Form | Password Reset | </title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS v5.0.2 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body>
<div class="container py-5">
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-6 col-sm-12 col-12 m-auto">
<form action="{{ route('login') }}" method="post" autocomplete="off">
@csrf
<div class="card shadow">
@if (Session::has("success"))
<div class="alert alert-success alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
{{ Session::get('success') }}
</div>
@elseif (Session::has("failed"))
<div class="alert alert-danger alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
{{ Session::get('failed') }}
</div>
@endif
<div class="card-header">
<h5 class="card-title"> Login </h5>
</div>
<div class="card-body px-4">
<div class="form-group py-2">
<label> Email </label>
<input type="email" name="email" class="form-control {{$errors->first('email') ? 'is-invalid' : ''}}" value="{{ old('email') }}" placeholder="Your Email">
{!! $errors->first('email', '<div class="invalid-feedback">:message</div>') !!}
</div>
<div class="form-group py-2">
<label> Password </label>
<input type="password" name="password" class="form-control {{$errors->first('password') ? 'is-invalid' : ''}}" value="{{ old('password') }}" placeholder="Your Password">
{!! $errors->first('password', '<div class="invalid-feedback">:message</div>') !!}
</div>
<div class="form-group text-end">
<a href="{{ route('forgot-password') }}" class="nav-link"> Forgot Password?</a>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-primary"> Login </button>
</div>
</div>
</form>
</div>
</div>
</div>
<!-- Bootstrap JavaScript Libraries -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
</body>
</html>
In the above blade file (login.blade.php), I have created a login form with email and password inputs. Also, there is a password reset link. When you will click on that, it will open the forgot-password.blade.php file.
Forgot Password Blade File
In the forgot-password.blade.php file there is a form with one input email and a submit button. Also, there is a redirection link to the login page if you already changed your password.
<!doctype html>
<html lang="en">
<head>
<title>Programming Fields | Login Form | Password Reset | </title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS v5.0.2 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body>
<div class="container py-5">
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-6 col-sm-12 col-12 m-auto">
<form action="{{ route('forgot-password') }}" method="post" autocomplete="off">
@csrf
<div class="card shadow">
@if (Session::has("success"))
<div class="alert alert-success alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
{{ Session::get('success') }}
</div>
@elseif (Session::has("failed"))
<div class="alert alert-danger alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
{{ Session::get('failed') }}
</div>
@endif
<div class="card-header">
<h5 class="card-title"> Forgot Password </h5>
</div>
<div class="card-body px-4">
<div class="form-group py-2">
<label> Email </label>
<input type="email" name="email" class="form-control {{$errors->first('email') ? 'is-invalid' : ''}}" value="{{ old('email') }}" placeholder="Your Email">
{!! $errors->first('email', '<div class="invalid-feedback">:message</div>') !!}
</div>
<div class="form-group text-end">
<a href="{{ route('login') }}" class="nav-link"> Back to Login</a>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-primary"> Reset Password </button>
</div>
</div>
</form>
</div>
</div>
</div>
<!-- Bootstrap JavaScript Libraries -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
</body>
</html>
Change Password Blade
In the change password blade file, there would be two password input fields. One for a new password and another for the password confirmation. From here you can change the new password.
<!doctype html>
<html lang="en">
<head>
<title>Programming Fields | Login Form | Password Reset | </title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS v5.0.2 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body>
<div class="container py-5">
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-6 col-sm-12 col-12 m-auto">
<form action="{{ route('reset-password') }}" method="post" autocomplete="off">
@csrf
@method('PUT')
<div class="card shadow">
@if (Session::has("success"))
<div class="alert alert-success alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
{{ Session::get('success') }}
</div>
@elseif (Session::has("failed"))
<div class="alert alert-danger alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
{{ Session::get('failed') }}
</div>
@endif
<div class="card-header">
<h5 class="card-title"> Change Password </h5>
</div>
<div class="card-body px-4">
<input type="hidden" name="email" value="{{ $email }} "/>
<div class="form-group py-2">
<label> Password </label>
<input type="password" name="password" class="form-control {{$errors->first('password') ? 'is-invalid' : ''}}" value="{{ old('password') }}" placeholder="New Password">
{!! $errors->first('password', '<div class="invalid-feedback">:message</div>') !!}
</div>
<div class="form-group py-2">
<label> Confirm Password </label>
<input type="password" name="confirm_password" class="form-control {{$errors->first('confirm_password') ? 'is-invalid' : ''}}" value="{{ old('confirm_password') }}" placeholder="Confirm Password">
{!! $errors->first('confirm_password', '<div class="invalid-feedback">:message</div>') !!}
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-primary"> Change Password </button>
</div>
</div>
</form>
</div>
</div>
</div>
<!-- Bootstrap JavaScript Libraries -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
</body>
</html>
After creating the views, it’s time to check the results.
How to Create Facebook Login in Laravel 8 Using Socialite
Reset Password Result
We will check all the results one by one. So, run the application to see the result.
Login Result
Open the login route as showing below. You will be having the below login screen. From here, you can login and navigate to the forgot password page.
There is validation for email and password fields. So, when you will hit the login button without having email and password. It will return the error message as showing below.
For the registration, I haven’t created any view here. So, I registered through the postman. Now, let’s try login functionality. Firstly, I am trying to login with an invalid email that is not registered.
In the response, I got the above message. Similarly, if you enter the invalid password but a correct and registered email. Then you will get the below result.
After entering the correct email and password, you will be logged in with the success message.
Forgot Password Result
When you will click on the Forgot Password link, it will open a new page as we specified the route for it. When you will hit the Reset Password button, it will show the validation error message.
When you will submit an email that is not registered then it will show the error message as showing below.
Now, try with the registered email address. Here, in the result, the email with the password reset link has sent to the registered email address.
Now, you can check for the email. In the mailbox, I got the email with the password reset button.
When you will click on the Reset your password button, it will redirect you to the change password blade file with the token.
Change Password Blade Result
In the change password form there is also a validation for the password and confirm password field. Also, the password must be confirmed.
After changing the password, you will be redirected to the login page with the success message.
When you will try to change the password with the same password reset link then it will show you the error. This password reset link is for one time. After changing your password the link is expired now. So, next time, you will need to generate the new password reset link with the new token.
Final Words
We implemented the forgot password functionality with the password reset link. The password reset link contained the token. The token is unique for every password reset link. So, it is verifying the user through the token. After changing the password the token will expire. The same link cannot be used twice for changing the password again. So, every time, you will need a new password reset link until it is expired. I hope this post will be very helpful for you to implement the custom password reset functionality in Laravel. If you have any doubts or query regarding this post then put your comments below. I will try to help you all. Thank you.
Onie Lequire says
This site was… how do I say it? Relevant!! Finally I’ve found something which helped me. Appreciate it!