DEV Community

loading...
Cover image for Protect your Laravel application with Google reCAPTCHA

Protect your Laravel application with Google reCAPTCHA

Luke
I'm probably in my pyjamas.
・3 min read

Preparing your application for spam and abuse should be high on your list of priorities when adding any public-facing form.

Fortunately, Google provides a free service called reCAPTCHA that protects your application by detecting whether the request is being made by a human or not, enabling you to prevent malicious requests from being submitted.

This guide will explain how to integrate Google’s Invisible reCAPTCHA in the form of a custom Laravel validation rule.

Assumptions

As this guide explains how to integrate Google’s Invisible reCAPTCHA into a pre-existing Laravel controller, you’ll need to have one set up. If you’re unfamiliar with Laravel controllers it’s highly recommended that you read the documentation before reading further.

Getting started

First of all, we’ll need to create a pair of keys from within the Google reCAPTCHA admin console and insert these into our application’s .env file:

GOOGLE_RECAPTCHA_SITE_KEY=WVWD4tdjCpMR1pIzDQRvA7Vl2mYSuSVc
GOOGLE_RECAPTCHA_SECRET_KEY=nIlEtWG4z8FVkGsS8YWufn9bCH9hSIVZ

As part of the verification process, we will need to make an API request to verify the user’s response, so we will need to install the Guzzle HTTP Client using Composer:

composer require guzzlehttp/guzzle

Using the Invisible reCAPTCHA

The simplest method of using the Invisible reCAPTCHA is by binding the challenge to our submit button:

<form method="post" action="{{ route('controller.post') }}" id="form">
    @csrf

    <label>Name
        <input type="text" name="name">
    </label>

    @error('name')
        <div>{{ $message }}</div>
    @enderror

    <button
        class="g-recaptcha"
        data-sitekey="{{ env('GOOGLE_RECAPTCHA_SITE_KEY') }}" 
        data-callback='onSubmit'
    >Submit</button>

    @error('g-recaptcha-response')
        <div>{{ $message }}</div>
    @enderror
</form>

<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<script>
    function onSubmit(token) {
        document.getElementById('form').submit();
    }
</script>

If Google deems the request to be suspicious it will present the user with the reCAPTCHA challenge when they attempt to submit the form.

Creating the validation rule

As we’ll be using reCAPTCHA to validate post requests, it makes sense to incorporate the functionality in the form of a custom validation rule.

To create the new rule object you may use the following Artisan command which will place the custom rule in the ‘App\Rules’ directory:

php artisan make:rule Recaptcha

Creating the Guzzle Client

As per Google’s reCAPTCHA documentation, we will need to verify the user’s response by posting our secret key and the user’s response token to the relevant endpoint:

use GuzzleHttp\Client;

public function passes($attribute, $value)
{
    $client = new Client;

    $response = $client->request(
        'POST', 'https://www.google.com/recaptcha/api/siteverify', [
            'form_params' => [
                'secret' => env('GOOGLE_RECAPTCHA_SECRET_KEY'),
                'response' => $value,
                'remoteip' => request()->ip(),
            ],
        ]
    );
}

In this example we also provide Google with the user’s IP address, this adds an extra layer of security by allowing Google to perform additional checks to determine whether or not the request is malicious. This is an optional parameter and can be removed.

Handling the response

We will begin by ensuring the request was accepted by checking for a status code of 200 before decoding the returned JSON object. Google informs us whether the user passed reCAPTCHA validation or not by providing a boolean ‘success’ value, which we will use to determine whether or not our custom rule has been passed:

if ($response->getStatusCode() == 200)
{
    $body = json_decode((string) $response->getBody());

    return $body->success;
}

return false;

Our custom validation rule should now look like this:

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

use GuzzleHttp\Client;

class Recaptcha implements Rule
{
    public function passes($attribute, $value)
    {
        $client = new Client();

        $response = $client->request(
            'POST', 'https://www.google.com/recaptcha/api/siteverify', [
                'form_params' => [
                    'secret' => env('GOOGLE_RECAPTCHA_SECRET_KEY'),
                    'response' => $value,
                    'remoteip' => request()->ip(),
                ],
            ]
        );

        if ($response->getStatusCode() == 200)
        {
            $body = json_decode((string) $response->getBody());

            return $body->success;
        }

        return false;
    }

    public function message()
    {
        return 'Failed reCAPTCHA validation.';
    }
}

Attaching the validation rule

Now that our custom validation rule has been defined, we’ll need to attach it to our validator by passing an instance of the rule object with the other validation rules:

use App\Rules\Recaptcha;

$request->validate([
    'g-recaptcha-response' => [ 'required', 'string', new Recaptcha ]
]);

Conclusion

There we have it! Google’s reCAPTCHA service is now integrated with our Laravel application. If Google deems that the request is legitimate the user journey will not be affected, but if anything suspicious is detected the user will be presented with the reCAPTCHA challenge upon clicking the submit button.

Our Laravel application is now protected from spam and abuse by utilising Google’s reCAPTCHA service as a custom validator rule.

Discussion (0)