We're live-coding on Twitch! Join us!
Password Verification Using Laravel Form Request

Password Verification Using Laravel Form Request

Code

You might have observed in a lot of applications, some parts require you to re-enter your password in order to proceed further or perform a desired action.

An example of it is Twitter's edit password section. In order to set a new password, you must also provide your current password.

Such a strategy works well as a double check and provides your application an extra layer of security.

In this quick tip, I am going to show you how to add such a functionality to your Laravel application using Laravel's form request.

A Primer on Form Request

So, what is a form request?

Let us start by having a look at Laravel's official documentation.

Form requests are custom request classes that contain validation logic.

As someone who has deep-dived into form requests, I would say they are not limited to performing validation.

Essential Reading: Learn React from Scratch! (2019 Edition)

For a start, you can also authorize a user against an action using a form request.

Form requests also help to keep your controller actions a lot cleaner.

Instead of dumping all the validation and conditional statements inside a controller action, you should opt to use a form request.

And finally, form requests in a way, serve as a component to filter requests before they reach your controller actions.

For this quick tip, I am going to assume you have the built-in Laravel authentication set up and working as it is beyond the scope of this tutorial to show how to set up Laravel authentication.

Creating the View

We are going to start by setting up a user's edit profile page.

At the time of writing this tutorial, artisan command utility does not generate views so we will need to create the view manually.

Create the file resources/views/profile/edit.blade.php and add the following code.

@extends('layouts.app')

@section('content')
<div class="container">
    @if (session('info'))
        <div class="row">
            <div class="col-md-12">
                <div class="alert alert-success alert-dismissible">
                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
                    {{ session('info') }}
                </div>
            </div>
        </div>        
    @elseif (session('error'))
        <div class="row">
            <div class="col-md-12">
                <div class="alert alert-danger alert-dismissible">
                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
                    {{ session('error') }}
                </div>
            </div>
        </div>
    @endif
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Update Profile</div>

                <div class="panel-body">
                    <form class="form-horizontal" method="POST" action="{{ route('profile.update', ['user' => $user]) }}">
                        {{ csrf_field() }}
                        {{ method_field('PUT') }}
                        <div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
                            <label for="name" class="col-md-4 control-label">Name</label>
                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control" name="name" value="{{ $user->name }}">

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

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

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

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

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

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

                        <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">

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

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

Our edit profile page is pretty simple.

We check for an info and error flash message and display it to the user.

It has a name, password, password_confirmation, and current_password field.

The way we want it to work is whenever a user makes a change, they have to provide the correct current_password field to commit the update to the database.

The password and password_confirmation fields will allow the user to change their password. If they are both left empty, the user's current password will be retained and no changes will be made to their stored password.

The major players in our view are the password, password_confirmation, and current_password fields.

As for the name field, it serves as an example to expand upon and add more fields for your case.

Creating the Form Request

Now onto the most crucial part of this tutorial.

Execute the following command to create a form request.

$ php artisan make:request UpdateProfile

Artisan Make Request

The above command will create a file named app/Http/Requests/UpdateProfile.php.

All code changes in this section will be made to this file.

The first thing we need to do is alias Laravel's Hash facade before the class declaration.

use Illuminate\Support\Facades\Hash;

Next, we need to return true from our authorize method since we are not performing authorization in our form request.

/**
 * Determine if the user is authorized to make this request.
 *
 * @return bool
 */
public function authorize()
{
    return true;
}

Our rules method will return the array outlining the validation rules for this request.

/**
 * Get the validation rules that apply to the request.
 *
 * @return array
 */
public function rules()
{
    return [
        'name' => 'required|string|max:255',
        'password' => 'nullable|required_with:password_confirmation|string|confirmed',
        'current_password' => 'required',
    ];
}

The name and current_password rules are pretty self-explanatory.

The password rules states that the password will be confirmed using the confirmed declaration.

It also declares required_with:password_confirmation which means if the user provides a password confirmation, they should also provide a password.

These validation rules will be checked automatically upon every request once we type-hint it in our controller action(which we will do later).

The last thing we need to do is declare a withValidator method on our request which is passed the fully constructed validator instance before any of the validation rules fire.

/**
 * Configure the validator instance.
 *
 * @param  \Illuminate\Validation\Validator  $validator
 * @return void
 */
public function withValidator($validator)
{
    // checks user current password
    // before making changes
    $validator->after(function ($validator) {
        if ( !Hash::check($this->current_password, $this->user()->password) ) {
            $validator->errors()->add('current_password', 'Your current password is incorrect.');
        }
    });
    return;
 }

Inside our withValdator method, we have added an after hook, a function which will be executed after all the validation checks have been made.

In our after hook, we have simply compared the user's provided password with their password set in the database.

The $this->current_password gives us the current_password form field value whereas Laravel allows us to access the currently authenticated user using $this->user() so $this->user()->password gives us the user's hashed password saved in the database.

The two passwords are compared using the Hash facade's check method.

If the hash check fails, an error is added to the validator with the key current_password using $validator->errors()->add('current_password', 'Your current password is incorrect.').

Here is our complete UpdateProfile form request.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

use Illuminate\Support\Facades\Hash;

class UpdateProfile extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|string|max:255',
            'password' => 'nullable|required_with:password_confirmation|string|confirmed',
            'current_password' => 'required',
        ];
    }

    /**
     * Configure the validator instance.
     *
     * @param  \Illuminate\Validation\Validator  $validator
     * @return void
     */
    public function withValidator($validator)
    {
        // checks user current password
        // before making changes
        $validator->after(function ($validator) {
            if ( !Hash::check($this->current_password, $this->user()->password) ) {
                $validator->errors()->add('current_password', 'Your current password is incorrect.');
            }
        });
        return;
    }
}

The Controller Action

To use our form request, we will need to type-hint it in our controller action.

Execute the following command to generate the profile controller.

$ php artisan make:controller ProfileController

Artisan Make Controller

Open the file app/Http/Controllers/ProfileController.php and add the following controller actions.

public function __construct()
{
    $this->middleware('auth');
}

/**
 * Show the form for editing the specified resource.
 *
 * @param  \App\User  $user
 * @return \Illuminate\Http\Response
 */
public function edit(Request $request, User $user)
{
    // user
    $viewData = [
        'user' => $user,
    ];
    // render view with data
    return view('profile.edit', $viewData);
}

/**
 * Update the specified resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \App\User  $user
 * @return \Illuminate\Http\Response
 */
public function update(UpdateProfile $request, User $user)
{
    // form data
    $data = $request->all();
    $user->update($data);
    return redirect(route('profile.edit', ['user' => $user]))
                ->with('info', 'Your profile has been updated successfully.');
}

The profile controller's constructor sets the auth middleware to make sure a user is logged in before editing his/her profile.

The edit action serves the view with the view data whereas the update action updates the user profile and redirects back to the edit profile page with the corresponding flash message.

Notice the signature of the edit action where we have type-hinted the UpdateProfile request which is all we need to do to fire the validations inside our UpdateProfile form request.

You will also need to alias the form request and the user model before the controller class declaration.

use App\Http\Requests\UpdateProfile;
use App\User;

Setting up the Route

Open the file app/routes/web.php and add the following code to tie in the controller actions.

Route::get('/profile/{user}/edit', 'ProfileController@edit')->name('profile.edit');
Route::put('/profile/{user}', 'ProfileController@update')->name('profile.update');

Adding Final Touches

Based on the validation rule we added earlier to our form request, it is possible for a null password to pass through.

Under no circumstances would a user(or an application developer) want their password to be set to null or an empty string.

To make sure a user password is set only when they provide one, we are going to use Eloquent ORM's mutators.

Open the file app/User.php and add the following code.

// Only accept a valid password and 
// hash a password before saving
public function setPasswordAttribute($password)
{
    if ( $password !== null & $password !== "" )
    {
        $this->attributes['password'] = bcrypt($password);
    }
}

Eloquent mutators have to follow the naming scheme set<camel-cased-attribute-name>Attribute.

Since we are declaring a mutator for the password attribute hence we have named the mutator setPasswordAttribute.

The mutator function is passed the value being set which in our mutator is the $password variable.

In our mutator, we simply check the $password variable is not null and an empty string and set it on our model using $this->attributes['password'].

Also note the password is hashed before saving so we do not have to do it elsewhere in our application.

The default Laravel Auth/RegisterController and Auth/ResetPasswordController also hash the password before persisting it to the database so we need to update the create and resetPassword method in the respective controllers after declaring the above mutator.

Open the file app/Http/Controllers/Auth/RegisterController.php and add the following code.

/**
 * Create a new user instance after a valid registration.
 *
 * @param  array  $data
 * @return \App\User
 */
protected function create(array $data)
{
    return User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => $data['password'],
    ]);
}

Open the file app/Http/Controllers/Auth/ResetPasswordController.php and add the following code.

/**
 * Reset the given user's password.
 *
 * @param  \Illuminate\Contracts\Auth\CanResetPassword  $user
 * @param  string  $password
 * @return void
 */
protected function resetPassword($user, $password)
{
    $user->password = $password;

    $user->setRememberToken(Str::random(60));

    $user->save();

    event(new PasswordReset($user));

    $this->guard()->login($user);
}

For the ResetPasswordController, you will also need to alias the respective classes used before the class declaration.

use Illuminate\Support\Str;
use Illuminate\Auth\Events\PasswordReset;

Curtains

We are all done and our password verification works as expected.

A test run of the application when providing an incorrect password or none yields the following behaviour demonstrated by the screenshots.

Incorrect Password

Password Required

Is there a better way to accomplish this neat check in Laravel? I would love to hear about it in your comments.

I hope you found this tutorial interesting and knowledgeable. Until my next piece, happy coding!

Like this article? Follow @nomanurrehman on Twitter