Codementor Events

Form validation and user input sanitization tricks in laravel

Published Feb 16, 2020
Form validation and user input sanitization tricks in laravel

A web application is hosted on a server, as we all know. General public can access a web application through browser, mobile application and many other ways. So if anyone is hosting his application for public use then he pays for the server resources. Let’s put aside general user authentication part because hacker can simply signup to an application, what if the owner/developer of the application is not well versed will input security. Then simply, username field can accept email and vise versa. Getting my point ?

Security is the very first step a web developer thinks of. Be it data security, resources security. Here in this article I am going to give a brief explanation on form validation and input sanitization.

Form validation

Forms are an integral part of a web application. Generally, they are used to POST data from one web page to another for processing.

A typical sign up form you may have come across looks like this.

Laravel validation applied on sign up form

Though laravel provide a basic security layer in web form, that is CSRF tokens.

“A CSRF token is a random, hard-to-guess string. On a page with a form you want to protect, the server would generate a random string, the CSRF token, add it to the form as a hidden field and also remember it somehow, either by storing it in the session or by setting a cookie containing the value.” – Stack Overflow

Once CSRF part is done then comes the input validations. In laravel, request input values can be validated using Validator facade. For example:

AuthController.php

<?php

Use Validator; //using facade alias
public class AuthController{
public function signup(Request $request)
{
$validator = Validator::make($request->all(), [
    'username' => 'required',
    'email' => 'required | email'
]);

if($validator->fails()) {
  return redirect()->back()
    ->withInput()
    ->withErrors($validator);
}
}
}

Above code is self explanatory, still let me explain it.

Controller AuthController, uses Validator facade and in signup function, we are validating all the request input values coming from a user while registration.

Second parameter of make() accepts an array of request values. The username is required, email is required too, but it also has to be in email format, that’s it. If any one of the above rules fail, validator also fails and redirects the user back to the last page with old inputs given as well as with error messages.

Some of the laravel validation rules provided by default

  • required: tells laravel to only accept if the value is not null, because laravel automatically converts empty strings into null.
  • email: string has be in email format, eg. johndoe.1992@example.com
  • sometimes: sometimes a web form may have an input field and sometimes it may be there at all due to some conditions there. In that case you may specify ‘sometimes | required’

Custom validation rule in laravel

Apart from default rules you may also create your custom rule and apply in validator using artisan commands. For example.

php artisan make:rule ConsolidatedLocation

Above command will generate a file in App/Rules directory with name ConsolidatedLocation with 3 empty methods, _constructs(), passes(), message(). In this example I have put my own rule definition

<?php

namespace App\Rules;

use App\Models\District;
use App\Models\State;
use App\Models\Taluka;
use Illuminate\Contracts\Validation\Rule;

class ConsolidatedLocation implements Rule
{
    protected $val;

    /**
    * Create a new rule instance.
    *
    * @return void
    */
    public function __construct()
    {
        //
    }

    /**
    * Determine if the validation rule passes.
    *
    * @param  string $attribute
    * @param  mixed $value
    * @return bool
    */
    public function passes($attribute, $value)
    {
        $this->val = $value;
        //first check if format is valid: taluka-district-state

        $array_value = explode('-', $value);

        $setStatus = false;

        if (true == (count($array_value) == 3)) ;
        {
            $setStatus = true;
        }


        if (true == $setStatus) {
            //now check each corresponding value at index

            if (false == Taluka::findByName($array_value[0])) {
                $setStatus = false;
            }

            if (false == District::findByName($array_value[1])) {
                $setStatus = false;
            }

            if (false == State::findByName($array_value[2])) {
                $setStatus = false;
            }
        }

        return $setStatus;

    }

    /**
    * Get the validation error message.
    *
    * @return string
    */
    public function message()
    {
        return 'Invalid location ' . $this->val;
    }
}

In controller,

$validator = Validator::make($request->all(), ['location' => ['required' , new ConsolidatedLocation()]);

Displaying error message to user after form validation gets failed

Let’s take an example for changing a password.

Laravel custom validation rule and showing error messages for better user interface.

Below is its blade contents.

<form action="{{ route('action.change.password') }}" method="POST">
                <div class="row">

                    <div class="mx-auto col-sm-6">
                        <div class="form-group">
                            <label for="current_password" class="text-uppercase">Current Password</label>
                            <div class="input-group mb-3">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fa fa-key"></i></span>
                                </div>
                                <input type="password" class="form-control" name="current_password"
                                      placeholder="Current Password">
                            </div>
                            <div class="text-danger">{{ $errors->first("current_password") }}</div>

                        </div>
                        <div class="form-group">
                            <label for="new_password" class="text-uppercase">New Password</label>
                            <div class="input-group mb-3">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fa fa-key"></i></span>
                                </div>
                                <input type="password" class="form-control" name="new_password"
                                      placeholder="New Password">
                            </div>
                            <div class="text-danger">{{ $errors->first("new_password") }}</div>

                        </div>

                        <div class="form-group">
                            <label for="confirm_new_password" class="text-uppercase">Confirm New Password</label>
                            <div class="input-group mb-3">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fa fa-key"></i></span>
                                </div>
                                <input type="password" class="form-control" name="confirm_new_password"
                                      placeholder="New Password">
                            </div>
                            <div class="text-danger">{{ $errors->first("confirm_new_password") }}</div>

                        </div>
                        {{ csrf_field() }}

                        @isset($password_not_matched)
                            <div class="text-danger">Current Password Not Matched</div>
                        @endisset
                        <div class="form-group pt-2">

                            <button type="submit" class="btn btn-success btn-lg btn-block ">Change Password</button>

                        </div>
                    </div>
                    <div class="col-sm-6">
                    </div>
                </div>
            </form>

And this is the method which is called by the controller.

public function changePassword(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'current_password' => 'required',
            'new_password' => 'required',
            'confirm_new_password' => 'required|same:new_password',
        ]);

        if ($validator->fails()) {
            return redirect()->back()->withErrors($validator);
        }
}

Thus any time the validator fails, it will be redirected back to my form with error message under the failed inputs like this:

<div class="text-danger">{{ $errors->first("<INPUT_NAME>") }}</div>

User input sanitization

Validation comes second after input sanitization because we need to sanitize every value coming from user end and then apply Validations. Unsanitized values open security vulnerabilities to XSS (Cross Site Scripting) attacks. 

XSS attacks

According to OWASP, Cross-Site Scripting (XSS) attacks are a type of injection, in which malicious scripts are injected into otherwise benign and trusted websites. XSS attacks occur when an attacker uses a web application to send malicious code, generally in the form of a browser side script, to a different end user. Flaws that allow these attacks to succeed are quite widespread and occur anywhere a web application uses input from a user in the output it generates without validating or encoding it.

Accordingly, before an XSS attack happens, we need to sanitize the values coming from user / client / browser. We are going to create a middleware for that.

How to create an xss middleware in laravel ?

Fire the following command to make a middleware

php artisan make:middleware XssSanitizer

Now, you can see new file in app/Http/Middleware/XssSanitizer.php and just put bellow code in your XssSanitizer.php file.

XssSanitize.php

namespace App\Http\Middleware;

use Closure;

use Illuminate\Http\Request;

class XssSanitizer

{

    public function handle(Request $request, Closure $next)

    {

        $input = $request->all();

        array_walk_recursive($input, function(&$input) {

            $input = strip_tags($input);

        });

        $request->merge($input);

        return $next($request);

    }

}

Now register our middleware in our app/Http/Kernel.php file. and add following line in $routeMiddleware array.

Kernel.php

class Kernel extends HttpKernel

{

    ....

    protected $routeMiddleware = [

        'auth' => \App\Http\Middleware\Authenticate::class,

        ....

        'XssSanitizer' => \App\Http\Middleware\XssSanitizer::class,

    ];

}

Now using XssSanitizer middleware in our routes.

Route::group(['middleware' => ['XssSanitizer']], function () {

  Route::get('view-register', 'RegisterController@viewRegisterPage');


  Route::post('register', 'RegisterController@registerAction);

});

Ebooks available now

You can download this article’s PDF eBooks for offline reading from below:

Issuu

Slide Share

Edocr

Discover and read more posts from Decode Web
get started
post comments1Reply
AurélienG
4 years ago

Bonjour, merci pour ce tuto, cependant, je ne recommande pas strip_tags pour la sécurisation des entrée (même avis que PHP ).