Latest Posts

Adding Google reCAPTCHA to Livewire components

Tutorials

Adding ReCaptcha to your forms is a necessary step when handling public form submissions, but adding it for Livewire is a little trickier. I'll explain how I go about this process.


Spam is a problem for any developer, and it's only getting worse! Bots are getting smarter, farms of people are busy submitting rubbish to our forms and the worlds climate is causing a huge increase in cyber probing.

There's lots of reasons why your forms might get spammed, from attempts to flood another users inbox, probing your systems for vulnerabilities to phishing attempts or even cyber propaganda.

So lets help keep the spam bots at bay by using Google reCAPTCHA v3. This is the 'invisible' reCAPTCHA service by Google - invisible apart from the huge reCAPTCHA icon it puts in the bottom corner of your website of course.

Google reCAPTCHA Dashboard As Livewire uses API requests, it's a little tricker to get setup than a typical form - but that's no reason to skip the spam prevention altogether. So without further ado...

Getting set up

If you haven't already done so, you'll need to generate your reCAPTCHA credentials. There is a public key, for use on the front end, and a secret key for use in API calls to Google. You can register credentials by visiting the Google reCAPTCHA console

Once you've got your keys, we need to tell Laravel about them. You can do this by creating a recaptcha property within your config/services.php file:

    'slack' => [
        ...
    ],

    'recaptcha' => [
        'public_key' => env('RECAPTCHA_PUBLIC_KEY'),
        'secret_key' => env('RECAPTCHA_SECRET_KEY'),
    ],
];

Notice that we've referenced environment variables, as our keys are subject to change and shouldn't be committed to our version control, we'll set them within our .env file:

# Google reCAPTCHA
RECAPTCHA_PUBLIC_KEY=yourPublicKey
RECAPTCHA_SECRET_KEY=yourSecretKey

Configure your Livewire component

Frontend

First, we'll get the frontend part of the component configured.

We'll utilise Alpine (already installed with Livewire) to initialise our form with reCAPTCHA. This is done within the x-data HTML attribute. Within this, we are setting our public reCAPTCHA key using Laravel Blade's @js helper - ensuring it's properly escaped.

<form
		@submit.prevent="doCaptcha"
    x-data="{
        siteKey: @js(config('services.recaptcha.public_key')),
        init() {
            // load our recaptcha.
            if (!window.recaptcha) {
                const script = document.createElement('script');
                script.src = 'https://www.google.com/recaptcha/api.js?render=' + this.siteKey;
                document.body.append(script);
            }
        },

        doCaptcha() {
            grecaptcha.execute(this.siteKey, {action: 'submit'}).then(token => {
                Livewire.dispatch('formSubmitted', {token: token});
            });
        },
    }">
	
	<!-- your form contents here -->
	
	<button type="submit">Submit!</button>
</form>

Our init method will get automatically called, and checks for the presence of reCAPTCHA, and sets it up it if it's not yet ready.

Finally, our doCaptcha method will be executed when we submit our form, in turn emitting the Livewire event formSubmitted, which will be sent to the server, along with the reCAPTCHA token generated by Google.

If you've done this correctly, you should now see the reCAPTCHA icon at the bottom of the website. If you don't see this, I recommend you check your console first, to see what's going on.

Backend

So our frontend is now emitting the submission for our form, we now need to listen for that submission. We can achieve this with PHP's awesome Attributes and Livewire's On attribute hook.

use Livewire\Attribute\On;

#[On('formSubmitted')]
public function submit($token): void
{
    $this->validate();
    $this->validateRecaptcha($token);
			
    // ToDo: we've validated, now handle our submission!
}

Now you'll see above that in addition to our standard Livewire validation, we're calling the validateRecaptcha method. We've already passed the token from the frontend, along with any Livewire data when we dispatched our event from Alpine. Now all we need to do, is confirm with Google, that the token we have is valid.

It's validation time!

There are lots of plugins to handle reCAPTCHA validation, but it's a surprisingly simple API call. We can do this in an additional method inside the Livewire component. We'll call this method validateRecaptcha.

We'll use Laravel's HTTP Facade to make our request to Google, along with our secret from our config file, the token we have generated and the users IP address - the IP is optional, but it helps in stopping those pesky bots!

If the token is deemed invalid, we'll throw a ValidationException with a recaptcha error message. This will get gracefully caught by Livewire and we can then output the error message in the frontend.

Otherwise, we'll continue processing on to the next steps of our submit method.

use Illuminate\Support\Facades\Http;
use Illuminate\Validation\ValidationException;

protected function validateRecaptcha(string $token): void
{
    // validate Google reCaptcha.
    $response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
        'secret' => config('services.recaptcha.secret_key'),
        'response' => $token,
        'remoteip' => request()->ip(),
    ]);

    $throw = fn ($message) => throw ValidationException::withMessages(['recaptcha' => $message]);

    if (! $response->successful() || ! $response->json('success')) {
        $throw($response->json(['error-codes'])[0] ?? 'An error occurred.');
    }

    // if response was score based (the higher the score, the more trustworthy the request)
    if ($response->json('score') < 0.6) {
        $throw('We were unable to verify that you\'re not a robot. Please try again.');
    }
}

If no exception is thrown by the validateRecaptcha method, then we can continue on processing our data. Otherwise, we'll be sent back to the frontend, and can render out our error within the Livewire component. An example would be something like this:

@error('recaptcha')
    <div class="bg-red-300 text-red-700 p-3 rounded">{{ $message }}</div>
@enderror

And that's it! You can now walk through your process, test it all out and iron out any issues.

If you've found this useful, please consider subscribing for future updates!