Some application which contains sensitive data requires you to change your password every N number of days. Applications in Banking sector or others which contain more sensitive data usually follow this approach.

In this article we will cover on how to implement password expiry feature on top of Laravel Basic Authentication. Before we go into the steps make sure you have a Laravel Setup with Basic Laravel Authentication System in place.

Before we dig into the steps, Let's take a moment to understand of the application flow we are trying to acheive. Our application will store the timestamp of last updated user password (via password reset, user regestration or change password).

Everytime user logs into the application we will check if the password updation date has crossed 30 days. If yes, we will log him out and force him to change the password. Once the password is updated we will update the timestamp in our database and will allow user to Log into the application.

Here is what the flow will look like.

Alright, Let's dig into the steps.

Model, Migration and Database Table for Password Expiration Data 

We want a new table in our database, where we can store the data related to password expiration. Following is the data we are looking to store against all users.

  • Number of days in which password will expire.
  • Last time the password was updated.

I am keeping Number of days for password expiration in database for each user, since we may want to configurable as per user. And allow user the option to shorten the number of days (if they want tighter security for themselves). If you want to the Number of days to be fixed for all user's in your applicaiton you can avoid storing it in the database table.

Let's go ahead and create the Model and migration file for the new table.

php artisan make:model PasswordSecurity -m

Run this command in command line on your project root. This will create a new Model Class as well as the migration file to create the new table.

Open the newly created migration file create_password_securities under App \ Database \ Migrations folder and modify the up() method to add the new fields in the table.

    public function up()
    {
        Schema::create('password_securities', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id');
            $table->tinyInteger('password_expiry_days');
            $table->timestamp('password_updated_at');
            $table->timestamps();
        });
    }

As the name suggests, we have added new fields to be added in the database table, password_expiry_days will contain the integer value of number of days in which user password will expire and password_updated_at contains a timestamp value of last password updation.

Let's go ahead and run the migration

php artisan migrate:refresh

Defining Model Relationships

Next, let's go ahead and define our relationship between User and PasswordSecurity Model.

As it looks a user will have only one entry in the password_securities table. We will setup a one-to-one relationship between these two..

Open User.php Model class located at App directory and add the following code.

    public function passwordSecurity()
    {
        return $this->hasOne('App\PasswordSecurity');
    }

Open PasswordSecurity.php Model class located under App direcrort and add the following method.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class PasswordSecurity extends Model
{

    protected $guarded = [];

    public function user()
    {
        return $this->belongsTo('App\User');
    }
}

Setting up Password Expiration Data

We need to setup default data in our password expiration data in password_securities table. We will add the in this table as soon as the user registers for our application.

Open RegisterController.php which is located under App / Http / Conrtollers / Auth and modify its create method to include code for creating the default entry of PasswordSecurity.

     protected function create(array $data)
    {

        $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);

        $passwordSecurity = PasswordSecurity::create([
            'user_id' => $user->id,
            'password_expiry_days' => 30,
            'password_updated_at' => Carbon::now(),
        ]);

        return $user;
    }

Just after User is created, we insert our default data in passwordSecurity table as well. You need to include use Carbon\Carbon; in your RegisterController.php imports.

Note : You will need to update the password_updated_at timestamp whenever user changes or resets his password. Thus make sure you modify the code of ChangePassword and ResetPassword functionality if you have in your application.

 

Checking for Password Expiration on Login

Now since we have necessary table and Model file in place to store the details of user's password expiration. Let's modify our LoginController.php to add the code of checking for password expiration every time user logs in.

We will override the authenticated() method of AuthenticatesUsers trait which is used by LoginController. authenticated() method is executed everytime user is successfully logs in. Thus we will use this method to do our job of checking for password expiration.

Open LoginController.php located at App \ Http \ Controllers \ Auth and add the authenticated() method with following code.

public function authenticated(Request $request, $user)
{
    $request->session()->forget('password_expired_id');

    $password_updated_at = $user->passwordSecurity->password_updated_at;
    $password_expiry_days = $user->passwordSecurity->password_expiry_days;
    $password_expiry_at = Carbon::parse($password_updated_at)->addDays($password_expiry_days);
    if($password_expiry_at->lessThan(Carbon::now())){
        $request->session()->put('password_expired_id',$user->id);
        auth()->logout();
        return redirect('/passwordExpiration')->with('message', "Your Password is expired, You need to change your password.");
    }

    return redirect()->intended($this->redirectPath());
}

If you look into the code, we check for password expiry with the help of Carbon class.  If the password is expired, we log the user out and redirect him to passwordExpiration page where he can update his password.

Let's take some time to understand the session variable password_expired_id that we are creating just before logging the user out. Since we dont want random user to be able to access password expiration page. We are setting up a session variable for authentication.

Only user's who have password expired will have this session variable available to them and they can only be able to set a new password using passwordExpiration page.

Setting up New Password After Expiration

Let's look at the code where user can set their new password if their password is expired.

Add the following routes in your routes / web.php file.

Route::get('/passwordExpiration','Auth\PwdExpirationController@showPasswordExpirationForm');
Route::post('/passwordExpiration','Auth\PwdExpirationController@postPasswordExpiration')->name('passwordExpiration');

Add a new controller PwdExpirationController.php under your App / Http / Controllers / Auth directory and add following code into it.

<?php

namespace App\Http\Controllers\Auth;

use App\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;

class PwdExpirationController extends Controller
{

    public function showPasswordExpirationForm(Request $request){
        $password_expired_id = $request->session()->get('password_expired_id');
        if(!isset($password_expired_id)){
            return redirect('/login');
        }
        return view('auth.passwordExpiration');
    }

    public function postPasswordExpiration(Request $request){
        $password_expired_id = $request->session()->get('password_expired_id');
        if(!isset($password_expired_id)){
            return redirect('/login');
        }

        $user = User::find($password_expired_id);
        if (!(Hash::check($request->get('current-password'), $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();

        //Update password updation timestamp
        $user->passwordSecurity->password_updated_at = Carbon::now();
        $user->passwordSecurity->save();

        return redirect('/login')->with("status","Password changed successfully, You can now login !");
    }
}

The method showPasswordExpirationForm will accept the GET request and will be used to show the password expiration page to the user where he can set the the new password.

The method postPasswordExpiration will accept the POST request from the the form. It updates user password after completing a certain checks on the input provided by the user.

Once the pasword is changed, we go ahead and change our password_updated_at timestamp in password_securities table.

Also notice that we have restricted access to both the methods to user's who has session variable 'password_expired_id' set at their end.

Create a blade file passwordExpiration.blade.php under directory resources / views / auth and put following content in it. This the view file will be used by user to change their passwords when expired.

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Password Expired</div>

                    <div class="panel-body">
                        @if (session('error'))
                            <div class="alert alert-danger">
                                {{ session('error') }}
                            </div>
                        @endif
                        @if (session('success'))
                            <div class="alert alert-success">
                                {{ session('success') }}
                            </div>
                        @endif
                            @if (session('message'))
                                <div class="alert alert-info">
                                    {{ session('message') }}
                                </div>
                            @endif
                        <form class="form-horizontal" method="POST" action="{{ route('passwordExpiration') }}">
                            {{ csrf_field() }}

                            <div class="form-group{{ $errors->has('current-password') ? ' has-error' : '' }}">
                                <label for="new-password" class="col-md-4 control-label">Current Password</label>

                                <div class="col-md-6">
                                    <input id="current-password" type="password" class="form-control" name="current-password" required>

                                    @if ($errors->has('current-password'))
                                        <span class="help-block">
                                        <strong>{{ $errors->first('current-password') }}</strong>
                                    </span>
                                    @endif
                                </div>
                            </div>

                            <div class="form-group{{ $errors->has('new-password') ? ' has-error' : '' }}">
                                <label for="new-password" class="col-md-4 control-label">New Password</label>

                                <div class="col-md-6">
                                    <input id="new-password" type="password" class="form-control" name="new-password" required>

                                    @if ($errors->has('new-password'))
                                        <span class="help-block">
                                        <strong>{{ $errors->first('new-password') }}</strong>
                                    </span>
                                    @endif
                                </div>
                            </div>

                            <div class="form-group">
                                <label for="new-password-confirm" class="col-md-4 control-label">Confirm New Password</label>

                                <div class="col-md-6">
                                    <input id="new-password-confirm" type="password" class="form-control" name="new-password_confirmation" required>
                                </div>
                            </div>

                            <div class="form-group">
                                <div class="col-md-6 col-md-offset-4">
                                    <button type="submit" class="btn btn-primary">
                                        Change Password
                                    </button>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

 

That's all is required to implement password expired functionality in your application.

[caption id="attachment_955" align="aligncenter" width="1212"]Password Expired Screen Laravel Password Expired Screen Laravel[/caption]

 

[caption id="attachment_956" align="aligncenter" width="1190"]Password Expired Changed Successfully. Password Expired Changed Successfully.[/caption]

 

Comments