Explore the Value of Custom Rules and Data Validation (With Laravel) in Inspector

Written by valerio-barbera | Published 2022/03/26
Tech Story Tags: laravel | laravel-framework | php | form-validation | data-validation | programming | coding | software-development

TLDRHi, I'm Valerio Barbera, software engineer, founder and CTO at Inspector. Data validation is one of the fundamental features in any application and it is something developers manipulate almost every day. The value a software provides to users is often a function of the quality of data it is able to manage. Laravel ships with a lot of predefined validation rules you can immediately use in your controllers. Working on the Inspector backend we have identified some aspects of validating incoming data, which have an impact on the security and reliability of the application. Furthermore, thanks to custom rules you can easily extend the validation layer of your app with functionalities provided by external services. Let me start with a bit of context to clarify the role the validation layer plays in a backend service, then I’ll show you our implementations.via the TL;DR App

Photo by Mathew Schwartz on Unsplash

Hi, I'm Valerio Barbera, software engineer, founder and CTO at Inspector.

Data validation is one of the fundamental features in any application and it is something developers manipulate almost every day.

The value a software provides to users is often a function of the quality of data it is able to manage.

Laravel ships with a lot of predefined validation rules you can immediately use in your controllers.

Working on the Inspector backend we have identified some aspects of validating incoming data, which have an impact on the security and reliability of the application.

Furthermore, thanks to custom rules you can easily extend the validation layer of your app with functionalities provided by external services.

Let me start with a bit of context to clarify the role the validation layer plays in a backend service, then I’ll show you our implementations.

Laravel Validation layer

Data integrity and validation are important aspects of web development because they define the state of the app. If data are wrong, the application behaves wrong.

It's always important to validate data not only before storing it in the database but before doing anything with the data.

In the Laravel request lifecycle, an HTTP request sent by a client-first goes through middleware. Middleware deals with a mix of things between authentication and security.

Now, before the request enters the application, the data it carries must be validated.

There are two ways to accomplish data validation in Laravel: Inside the controllers, or using Form requests.

Validation in Controller

The easiest way of validation is performing it directly in the controller. At the start of each controller method you can first validate data:

<?php

namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
 
class UserController extends Controller
{
    public function store(Request $request)
    {
        $request->validate([
            'name' => 'required|string|min:3',
            'email' => 'required|email|min:6',
        ]);
 
        // here we know data are valid so we can pass them to database or other services
    }
}

Laravel will take care to return a 422 response code to the client if the data are not valid.

Use Form requests

If your validation rules are too complex you may want to encapsulate them in reusable classes avoiding messing up the controller.

Laravel provides the ability to wrap validation in a dedicated component called FormRequest.

First, create a form request class:

php artisan make:request StoreUserRequest

Then move your validation logic inside the rules method of the request class:

<?php
 
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
 
class StoreUserRequest 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|min:3',
            'email' => 'required|email|min:6',
        ];
    }
}

You can type-hint this new request class in the controller method instead of the original request class so Laravel will apply the validation rules automatically, and remove the validation statement:

<?php
 
namespace App\Http\Controllers;
 
use App\Http\Requests\StoreUserRequest;
 
class UserController extends Controller
{
    public function store(StoreUserRequest $request)
    {
        // here we know data are valid so we can pass them to database or other services
    }
}

Custom Validation Rules

Laravel provided a really well-developed validation layer. It can be easily extended by implementing custom rules to be reused in your code, or to extend the capability of your validation using external services.

Allow me to show you a real example of one of the custom rules we implemented in Inspector.

First, create the class that represents a validation rule in Laravel:

php artisan make:rule SecurePassword

The idea is to verify if the password is in the list of well-known insecure passwords. If it is, it will not pass the validation, forcing the user to use a less common string.

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class SecurePassword implements Rule
{
    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        return !in_array($value, [
            'picture1',
            'password',
            'password1',
            '12345678',
            '111111',
			
			...
        ]);
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return 'The chosen password is unsecure. Try again with a less common string.';
    }
}

Integrate With External Services

Talking about data validation there are a lot of SaaS services that can bring new capabilities in your validation layer in terms of security and reliability of the data collected.

I recommend you take a look at apilayer.com which provides a great set of REST services to deal with data.

In Inspector we use the mailboxlayer.com API to validate emails. The service is also able to detect fake email addresses, temporary addresses, and the actual existence of an email address using MX-Records and SMTP.

Add two configuration property to store the api keys of the new services in the config/service.php file:

return [

    ...,
	
    'mailboxlayer' => [
        'key' => env('MAILBOXLAYER_KEY'),
    ],

    'vatlayer' => [
        'key' => env('VATLAYER_KEY'),
    ],
	
];

Create the custom rule:

php artisan make:rule EmailSpam

Here is the complete code of the rule:

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class EmailSpam implements Rule
{
    /**
     * Determine if the validation rule passes.
     *
     * @param string $attribute
     * @param mixed $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        if (app()->environment('local')) {
            return true;
        }

        return !config('services.mailboxlayer.key') || $this->check($value);
    }

    /**
     * Perform email check.
     *
     * @param string $email
     * @return bool
     */
    protected function check(string $email): bool
    {
        try{
            $response = file_get_contents('https://apilayer.net/api/check?'.http_build_query([
                'access_key' => config('services.mailboxlayer.key'),
                'email' => '[mailbox-layer-account-email]',
                'smtp' => 1,
            ]));

            $response = json_decode($response, true);

            return $response['format_valid'] && !$response['disposable'];

        } catch (\Exception $exception) {
            report($exception);

            if (app()->environment('local')) {
                return false;
            }

            // Don't block production environment in case of apilayer error
            return true;
        }
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return 'Invalid email address.';
    }
}

Tips & Tricks

Validate Borders

Based on my experience I can suggest you to always validate not only the minimum size of the incoming fields but also the maximum size.

Don't wait for database errors that truncate too long strings and help your users to understand the limits of each field by the error messages returned during validation.

Ask for the Current Password

Every critical action should require a password confirmation.

You should always prompt the user to type the current password to authorize actions that can compromise the account accessibility, like change email, and change password.

This feature will improve security because also having physical access to the computer with the Inspector dashboard opened on the screen, a malicious user can’t change access credentials without knowing the current password. He can’t shut you out.

Here is our implementation of the current password verification:

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;

class CurrentPassword implements Rule
{
    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        return Hash::check($value, Auth::user()->password);
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return 'Your current password is incorrect.';
    }
}

New to Inspector?

Are you looking for a "code-driven" monitoring tool to identify technical problems in your applications automatically?

Get a monitoring environment specifically designed for software developers avoiding any server or infrastructure configuration.

Thanks to Inspector, you will never have the need to install things at the server level or make complex configuration in your cloud infrastructure to monitor your application in real-time.

Inspector works with a lightweight software library that you can install in your application like any other dependencies based on the technology you are using to develop your backend.

Check out the supported technology on our GitHub (https://github.com/inspector-apm).

Visit our website for more details: https://inspector.dev/laravel/


Written by valerio-barbera | Simple Code Execution Monitoring, built for developers
Published by HackerNoon on 2022/03/26