Implement Password Expiration & Force Reset in Laravel
Today I’m going to share how to implement password expiration and force reset password in Laravel.
I’m testing on Laravel 7.15. Let’s get started:
Table of Contents
- Install Laravel and Basic Configurations
- Generate Auth Scaffolding
- Create Model & Migration
- Set Model Relationships
- Modify Register Controller
- Check Password Expiration
- Create Password Security Controller
- Create Reset Form
- Define Routes and Test
Install Laravel and Basic Configurations
Each Laravel project needs this thing. That’s why I have written an article on this topic. Please see this part from here: Install Laravel and Basic Configurations.
Generate Auth Scaffolding
We’ve to enable auth scaffolding. Run these commands to enable auth scaffolding:
# install laravel ui
composer require laravel/ui --dev
# auth scaffolding
php artisan ui vue --auth
# finally run
npm i && npm run dev
Create Model & Migration
To keep password expiration data, we need a table. Run this command to create a model and migration:
php artisan make:model PasswordSecurity -m
Go to database\migrations folder and open create_password_securities_table.php file. Then update up()
function like this:
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();
});
}
Now run the migration:
php artisan migrate:refresh
Set Model Relationships
Navigate to app folder and open PasswordSecurity.php model. We need to add $fillable
array and need to set relation with User model. Let’s got this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class PasswordSecurity extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'user_id', 'password_expiry_days', 'password_updated_at'
];
// set relation
public function user()
{
return $this->belongsTo('App\User');
}
}
Open User.php from the same location and add this relation:
// set relation
public function passwordSecurity()
{
return $this->hasOne('App\PasswordSecurity');
}
Modify Register Controller
Open RegisterController.php from app\Http\Controllers\Auth folder and update create()
function:
use App\PasswordSecurity;
use Carbon\Carbon;
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
$passwordSecurity = PasswordSecurity::create([
'user_id' => $user->id,
'password_expiry_days' => 30,
'password_updated_at' => Carbon::now(),
]);
return $user;
}
I’ve set 30 days in password_expiry_days
. You can set your own days to reset the password.
Check Password Expiration
We’ll check password expiration from the login controller. We will override the authenticated()
method of AuthenticatesUsers
trait.
Open LoginController.php located at app\Http\Controllers\Auth and add the authenticated()
method:
use Carbon\Carbon;
// user authenticated
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('/reset-password')->with('message', "Your password is expired. You need to change your password.");
}
return redirect()->intended($this->redirectPath());
}
Create Password Security Controller
From this controller we’ll show password reset form & store new password. Create the controller:
php artisan make:controller Auth\PasswordSecurityController
Open PasswordSecurityController.php from app\Http\Controllers\Auth directory and paste this code:
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
class PasswordSecurityController extends Controller
{
// reset password form
public function resetPasswordForm(Request $request)
{
$password_expired_id = $request->session()->get('password_expired_id');
if (!isset($password_expired_id)) {
return redirect('/login');
}
return view('auth.reset_password');
}
// reset password
public function resetPassword(Request $request)
{
// check expire id
$password_expired_id = $request->session()->get('password_expired_id');
if (!isset($password_expired_id)) {
return redirect('/login');
}
// validate
$validatedData = $request->validate([
'current_password' => 'required',
'new_password' => 'required|string|min:6|confirmed',
]);
// the requests
$request_current_password = $request->current_password;
$request_new_password = $request->new_password;
$request_new_password_confirm = $request->new_password_confirm;
// the passwords matches
$user = User::find($password_expired_id);
if (!(Hash::check($request_current_password, $user->password))) {
return redirect()->back()->with("error", "Your current password does not matches with the password you provided. Please try again.");
}
// current password and new password are same
if (strcmp($request_current_password, $request->new_password) == 0) {
return redirect()->back()->with("error", "New password cannot be same as your current password. Please choose a different password.");
}
// new password and new password confirm doesn't match
if (strcmp($request_new_password, $request_new_password_confirm) == 1) {
return redirect()->back()->with("error", "New password doesn't match with confirm password.");
}
// change Password
$user->password = bcrypt($request->new_password);
$user->save();
// update password update time
$user->passwordSecurity->password_updated_at = Carbon::now();
$user->passwordSecurity->save();
return redirect('/login')->with("status", "Password changed successfully. Now you can login!");
}
}
Create Reset Form
Go to resources/views/auth folder and create a new file named reset_password.blade.php. After that open the file and paste this code:
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Password Expired') }}</div>
<div class="card-body">
@if (session('error'))
<div class="alert alert-danger alert-square">
{{ session('error') }}
</div>
@endif
@if (session('success'))
<div class="alert alert-success alert-square">
{{ session('success') }}
</div>
@endif
@if (session('message'))
<div class="alert alert-info alert-square">
{{ session('message') }}
</div>
@endif
<form method="POST" action="{{ route('resetPassword') }}">
@csrf
<div class="form-group{{ $errors->has('current_password') ? ' has-error' : '' }}">
<label for="current_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_confirm" 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
Define Routes and Test
Open web.php from routes folder and define 2 routes:
Route::get('/reset-password','Auth\PasswordSecurityController@resetPasswordForm');
Route::post('/reset-password','Auth\PasswordSecurityController@resetPassword')->name('resetPassword');
Our project is ready to test. Run the project and register a user. Then set old date & time at password_updated_at column in password_securities table. Then try to login and you’ll see the password expiration notice with form.
That’s it. You can download this project from GitHub. Thank you.
Md Obydullah
Software Engineer | Ethical Hacker & Cybersecurity...
Md Obydullah is a software engineer and full stack developer specialist at Laravel, Django, Vue.js, Node.js, Android, Linux Server, and Ethichal Hacking.