Implementing Password History with Laravel Authentication

Password History Laravel Error

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.

  1. Laravel Setup.
  2. Basic Laravel Authentication Configured.

Alright, Lets dig into the steps.

 

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.

  1. On User Registration.
  2. On Changing Password.
  3. 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.

Password History Laravel Error
Password History Laravel Error

Web Stuff Enthusiast.

Related Posts

7 comments On Implementing Password History with Laravel Authentication

  • Thanks for the nice tutorial. I have a couple of suggestions.
    1. You should not directly reference env() in y0ur controller. You should use config(). In other words, edit app/config/app.php and insert ‘password_history_num’ => env(‘PASSWORD_HISTORY_NUM’, 3). In your controller, use config(‘app.password_history_num’).
    2. You should not loop over the previous passwords and then re-hash them each time. It’s better to hash once and look in the table for the hashed value.

  • When you provide the migrate command – you have put

    php run migrate:refresh

    shouldn’t it be

    php artisan migrate
    (and not php artisan migrate:refresh)

    The migrate:refresh command will roll back all of your migrations and then execute the migrate command. This command effectively re-creates your entire database.

  • Why do not you use an Observer/Events for User model?

  • Hate everything about this concept, having such rules forces users to end up just doing things like chucking a 1 at the end.

    It also creates an audit log of the users password history making you a valuable target for advance hackers for such a lovely large password database. Imagine the passwords were reversed, you now have a plump catalog of that users password for usage in other places.

    It also contributes to a negative user experience causing frustration to the users, eg the login is failing, so you reset your password to a known secure one. Then it says that password is already used. When that was the password you tried in the first place.

  • Does not it involve with the password_reset table , how to make relationship in the model users, password_reset_table, and passwordHistory?

Leave a reply:

Your email address will not be published.

Site Footer