Tutorial

How to Implement Password Verification Using Laravel Form Request

Published on December 12, 2019
author

Noman Ur Rehman

How to Implement Password Verification Using Laravel Form Request

Introduction

Laravel form requests are special classes that extend the functionality of regular request classes, enabling advanced validation features. Form requests also help to keep your controller actions a lot cleaner, because you can move all your validation logic to the form request class. Another benefit is that it allows you to filter requests before they reach your controller actions.

In this guide, we’ll implement a password verification step to request that a user confirms their password before accessing an admin area. This practice works as a double check and provides your application an extra layer of security.

Prerequisites

To follow up with this guide, you’ll need a working Laravel 5.6+ application with the built-in Laravel authentication set up. Please check the official documentation for details on how to set this up.

Step 1 — 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’ll 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

In our “edit profile” page, 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.

Step 2 — Creating the Form Request

Now onto the most crucial part of this tutorial.

Execute the following command to create a form request.

  1. php artisan make:request UpdateProfile

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 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’ll 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 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;
    }
}

Step 3 — Setting Up the Controller

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

Execute the following command to generate the profile controller.

  1. php artisan make:controller ProfileController

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;

Step 4 — Setting up Protected Routes and Data Mutators

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');

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 check if the $password variable is not null or an empty string and set it in 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;

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

Conclusion

In this guide, we saw how to implement an additional password verification step to assert that a user is authorized to access an admin area. We’ve seen how to create and set up form requests to implement form validation within a Laravel application.

For more information about validation and form requests, you can check the official Laravel documentation.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Noman Ur Rehman

author

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more