In this article we will go over on how to implement Password History functionality on top of Laravel Authentication. Password History forces users or your application customers to choose fresh password instead of choosing one from their recently used password.
Thus Password History enforces a better security and forces user not to fall in trap of using a similar password which they are using on other websites.
Before we go into steps, following are the things that you need to have following things in place.
Alright, Lets dig into the steps.
Use coupon 5balloons on this Cloudways Affiliate URL to get special discount.
Model and Database table to Maintain Password History
Since we need to check if user is not re-using their old password, we need to maintain the list of their old passwords in a database table. Let’s create a Model and migration for the same.
php artisan make:model PasswordHistory -m
Adding -m
to the command makes sure that it will also create a migration file along with the Model. Open the newly created migration file create_password_histories
and update it to add new table fields.
public function up()
{
Schema::create('password_histories', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id');
$table->string('password');
$table->timestamps();
});
}
we have added two fields user_id
and password
, which will be used to maintain history of passwords for particular user.
Run the migrate command to create the new database tables.
php artisan migrate
Defining Eloquent Relationship
We will assign eloquent relationship between User
and PasswordHistory
Model. Since a user can have multiple passwords stored in the password_histories table. We will have one-to-many relationship between these two.
Open User.php
model file located under App directory, and add following method to it.
public function passwordHistories()
{
return $this->hasMany('App\PasswordHistory');
}
Open PasswordHistories.php
model file located under App directory, and modify it to add user relation.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class PasswordHistory extends Model
{
protected $guarded = [];
public function post()
{
return $this->belongsTo('App\User');
}
}
Creating Password History
Now since we have necessary database tables and Eloquent relationship in place, Let’s move on to the part where we will store user passwords. We will insert a record whenever user creates or changes his password. Following are the identified locations where we will need to insert a record into password_histories table.
- On User Registration.
- On Changing Password.
- On Resetting Password.
Let’s modify each of the above part of the code to accomodate for inserting a record into password_histories table as soon as user changing his password.
1. On User Registration
Open RegisterController.php
located under App / Controllers / Auth and modify the create method to add the following code.
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
$passwordHistory = PasswordHistory::create([
'user_id' => $user->id,
'password' => bcrypt($data['password'])
]);
return $user;
}
As you can see we are creating a new record of PasswordHistory just aftering registring the new user.
2. On Changing Password
For detailed guide into implementing the Change Password Functionliaty into your Laravel Project you can visit the link. You need to modify yout Chage-Password method to accomodate for creating a record into PasswordHistory table.
public function changePassword(Request $request){
if (!(Hash::check($request->get('current-password'), Auth::user()->password))) {
// The passwords matches
return redirect()->back()->with("error","Your current password does not matches with the password you provided. Please try again.");
}
if(strcmp($request->get('current-password'), $request->get('new-password')) == 0){
//Current password and new password are same
return redirect()->back()->with("error","New Password cannot be same as your current password. Please choose a different password.");
}
$validatedData = $request->validate([
'current-password' => 'required',
'new-password' => 'required|string|min:6|confirmed',
]);
//Change Password
$user->password = bcrypt($request->get('new-password'));
$user->save();
//entry into password history
$passwordHistory = PasswordHistory::create([
'user_id' => $user->id,
'password' => bcrypt($request->get('new-password'))
]);
return redirect()->back()->with("success","Password changed successfully !");
}
3. On Resetting Password
Since there is no explicit method which gets executed just after user resets his password, we need to create a listener for the PasswordReset Event which is fired just after user successfuly reset’s his password.
Create a new file ResetPasswordListener.php
under App / Listeners and following code into it.
<?php
namespace App\Listeners;
use App\PasswordHistory;
use Illuminate\Auth\Events\PasswordReset;
class ResetPasswordListener
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param OrderShipped $event
* @return void
*/
public function handle(PasswordReset $passwordReset)
{
$passwordHistory = PasswordHistory::create([
'user_id' => $passwordReset->user->id,
'password' => $passwordReset->user->password
]);
}
}
We also need to reigster new Event and its Listener in our EventServiceProvider.php
which is located under App / Providers.
Modify the $listen
array to include the following
'Illuminate\Auth\Events\PasswordReset' => [
'App\Listeners\ResetPasswordListener'
]
This is all is required to maintain user’s password history. Whenever user registers, changes or resets his password his history will be maintained in the password_histories table.
Enforce Password History.
Now let’s see an example of how we can enforce user to choose a fresh password instead of choosing one from the most recent one.
For this you will have to decide for your application how many old passwords to check which cannot be same as user’s new password. For the sake of this demonstration I have chosen the pasword history number to be 3.
Open your environment configuration file .env
location on the Project root and add the following entry.
PASSWORD_HISTORY_NUM = 3
Now let’s modify our change password method to validate if user’s new password is not same as his last 3 passwords. If yes we will redirect him back with the error message.
public function changePassword(Request $request){
if (!(Hash::check($request->get('current-password'), Auth::user()->password))) {
// The passwords matches
return redirect()->back()->with("error","Your current password does not matches with the password you provided. Please try again.");
}
if(strcmp($request->get('current-password'), $request->get('new-password')) == 0){
//Current password and new password are same
return redirect()->back()->with("error","New Password cannot be same as your current password. Please choose a different password.");
}
$validatedData = $request->validate([
'current-password' => 'required',
'new-password' => 'required|string|min:6|confirmed',
]);
//Check Password History
$user = Auth::user();
$passwordHistories = $user->passwordHistories()->take(env('PASSWORD_HISTORY_NUM'))->get();
foreach($passwordHistories as $passwordHistory){
echo $passwordHistory->password;
if (Hash::check($request->get('new-password'), $passwordHistory->password)) {
// The passwords matches
return redirect()->back()->with("error","Your new password can not be same as any of your recent passwords. Please choose a new password.");
}
}
//Change Password
$user->password = bcrypt($request->get('new-password'));
$user->save();
//entry into password history
$passwordHistory = PasswordHistory::create([
'user_id' => $user->id,
'password' => bcrypt($request->get('new-password'))
]);
return redirect()->back()->with("success","Password changed successfully !");
}
->take(3)
in the Eloquent query ensures that we get the top 3 password histories for the particular user.
User will be redirected back with error message on choosing a recent password.