[Solved] Add Recaptcha to Registration Form on Laravel 5.7 w/ Spark 7.0

Laravel Spark is a great for setting up user management, subscriptions, and an API quickly. Without Spark, these steps would take weeks. With spark it takes only a few hours.

But Spark does have some issues: mainly the documentation. For such and large system—particularly one that is paid ($99 per site)—there should be better documentation.

Context

Adding custom fields to the registration form is supposed to be easy. This customization is explicitly documented. However the steps do not work for added a Recaptcha to the registration form.

Normally, you would not need a captcha on the registration form because users have to pay to sign up. However, our app has a free tier. Another solution to this problem is requiring email verification. But email verification has its own problems.

Regardless, this is a problems others have had as well.

What is the issue

There are three problems with using Recaptcha on the registration page:

    1. Spark registration page does not support custom fields with dashes in the name. This is not documented. The Recaptcha custom field is called “g-recaptcha-response”.
    2. The Recaptcha field is created dynamically which does not work with the Vue powered form that ships with Spark.
    3. Spark registration calls the validator twice which causes Recaptcha validation to fail the second time.

The solution

  1. Remove dashes from the field name. Call it “grecaptcharesponse” instead of “g-recaptcha-response”.
  2. Manually put the recaptcha verification token into the “grecatpcharesponse” field before submitting the form
  3. Only call the validator once

Step by Step

  1. Create recaptcha validator
    php artisan make:rule Recaptcha
  2. Install Guzzle
    composer require guzzlehttp/guzzle
  3. Paste this into app/Rules/Recaptcha.php
<?php

namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use GuzzleHttp\Client;

class Recaptcha implements Rule
{
    public function passes($attribute, $value)
    {
        // this hack prevents the validator from failing the second time it is called.
        if (isset($GLOBALS['G_RECAPTCHA_RESULT']))
        {
            return $GLOBALS['G_RECAPTCHA_RESULT'];
        }

        $client = new Client();

        $response = $client->post(
            'https://www.google.com/recaptcha/api/siteverify',
            ['form_params'=>
                [
                    'secret' => env('RECAPTCHA_PRIVATE_KEY'),
                    'response' => $value
                ]
            ]
        );

        $body = json_decode((string)$response->getBody());
        $GLOBALS['G_RECAPTCHA_RESULT'] = $body->success;
        return $body->success;
    }

    public function message()
    {
        return 'Check the box "I\'m not a robot"';
    }
}
  1. Include the validator we just created at the top of your SparkServiceProvider.
    use App\Rules\Recaptcha;
  2. Add this to the booted method of your SparkServiceProvider:
Spark::validateUsersWith(function () {
    return [
        'name' => 'required|max:255',
        'grecaptcharesponse' => ['required', new Recaptcha],
        'email' => 'required|email|max:255|unique:users',
        'password' => 'required|confirmed|min:6',
        'terms' => 'required|accepted',
    ];
});
  1. Add this code to resources/js/app.js
Spark.forms.register = {
    grecaptcharesponse: 'abc'
};
  1. Add this to resources/views/vendor/spark/auth/register-common-form.blade.php where you want the recaptcha form to appear
<div class="form-group row">
    @push('scripts-header')
        <script src='https://www.google.com/recaptcha/api.js'></script>
    @endpush
    <label class="col-md-4 col-form-label text-md-right">Are you human?</label>

    <div class="col-md-6">
        <div class="g-recaptcha" data-sitekey="{{env('RECAPTCHA_PUBLIC_KEY')}}"></div>

        <span class="invalid-feedback" style="display: block;">
            @{{ registerForm.errors.get('grecaptcharesponse') }}
        </span>
    </div>
</div>
  1. Add this just before the closing head tag in your layout file:
    @stack['scripts-header']
  2. Update “sendRegistration” method in resources/js/spark/auth/register-stripe.js
sendRegistration() {
    if ($('#g-recaptcha-response').val())
    {
        this.registerForm.grecaptcharesponse = $('#g-recaptcha-response').val();
        grecaptcha.reset();
    }
    else
    {
        // put something here instead of an empty string so you get the 
        // Recaptcha error message instead of the "required" error message.
        this.registerForm.grecaptcharesponse = "abc";
    }

    Spark.post('/register', this.registerForm)
        .then(response => {
            window.location = response.redirect;
        });
}

Conclusion

That ended up being a little longer than I intended. Other gotchas you may encounter:

  • Laravel has recently moved the location of the assets files so your file structure may be different
  • You will need to publish the Spark vendor files in order to edit them

You can see this in action at BitcoinAbuse.com.