DEV Community

James Candan
James Candan

Posted on • Updated on

reCAPTCHA + Laravel + Vue

We'll implement a reCAPTCHA Vue package in the frontend and a PHP package for the backend. We'll ensure each step is working as expected along the way.

Table of contents

Prerequisites

Assumes you have a working Laravel 8 and Vue 2.0 project, Composer and NPM, and a Vue form to which you want to add reCAPTCHA.

This was built onto a project that uses InertiaJS. However, I believe this should work on a separated front-end and back-end. As such, each sub-section is prefixed with either (Laravel) or (Vue) to indicate to which side the changes belong.

1. Provide reCAPTCHA keys

We'll need a valid SITEKEY and SECRET from Google.

(Laravel) Store environment variables

Add keys to your .env file.

NOTE: Do not commit your .env file. You should commit a .env.example with secure values redacted.

RECAPTCHAV2_SITEKEY=<yoursitekeyhere>
RECAPTCHAV2_SECRET=<yoursecrethere>
Enter fullscreen mode Exit fullscreen mode

WARNING: Do not expose RECAPTCHAV2_SECRET to the front-end.

If using VueCLI, environment variables are embedded into the build, meaning anyone can view them by inspecting your app's files. If using VueCLI, Do not store this value in any .env file on Production, not even .env.production. They are auto-loaded by VueCLI. It is okay to expose the SITEKEY, but for the SECRET, use some other secure method in Production, such as setting environment variables without this dotenv file.

Define these environment variables in Laravel's configuration.

Create a config/recaptchav2.php file:

<?php
return [
    'origin' => env('RECAPTCHAV2_ORIGIN', 'https://www.google.com/recaptcha'),
    'sitekey' => env('RECAPTCHAV2_SITEKEY', ''),
    'secret' => env('RECAPTCHAV2_SECRET', '')
];
Enter fullscreen mode Exit fullscreen mode

(Laravel) Share environment variable with front-end

Here's that InertiaJS piece mentioned in the prerequisites above.

The gist of this change is that we want to share the RECAPTCHAV2_SITEKEY environment variable with the front-end.

If not using InertiaJS, you should be able to provide the environment variable to Vue some other way (such as mentioned above if using VueCLI).

Here's an InertiaJS way of doing it:

We do this with InertiaJS middleware in app/Http/Middleware/HandleInertiaRequests.php.

 public function share(Request $request)
 {
     return array_merge(parent::share($request), [
+        // Provide the reCAPTCHA site key to the front-end.
+        'recaptchav2_sitekey' => config('recaptchav2.sitekey'),
+        // DO NOT SHARE RECAPTCHAV2_SECRET.
     ]);
 }
Enter fullscreen mode Exit fullscreen mode

(Vue) Ensure successful share to front-end

In your Vue form somewhere, we'll temporarily plop in the following to ensure the key is being passed successfully from the backend.

 <template>
+  {{ $page.props.recaptchav2_sitekey }}
   ...
 </template>
Enter fullscreen mode Exit fullscreen mode

This may be different depending on the route taken to expose the environment variable. With VueCLI it would be {{ process.env.RECAPTCHAV2_SITEKEY }}.

Browsing the page should display your site key.

2. Prepare the front-end

We have a securely stored secret key, and can display the site key on the page. Now, let's get a working reCAPTCHA to appear on the page.

(Vue) Add reCAPTCHA Vue

Require the reCAPTCHA Vue package.

npm install --save @vue/composition-api vue-recaptcha
Enter fullscreen mode Exit fullscreen mode

Add the reCAPTCHA to your component.

 <template>
-  {{ $page.props.recaptchav2_sitekey }}
   <form>
     ...
+    <vue-recaptcha 
+      :sitekey="$page.props.recaptchav2_sitekey" 
+    />
   </form>
 </template>

+<script>
+import { VueRecaptcha } from 'vue-recaptcha';
+
+export default {
+  components: {
+    VueRecaptcha,
+  },
Enter fullscreen mode Exit fullscreen mode

At this point, you should have a visible, working reCAPTCHA on your page.

3. Prepare the back-end

(Laravel) Add middleware

composer require google/recaptcha
Enter fullscreen mode Exit fullscreen mode

Define a reCAPTCHA middleware class.

<?php
/**
 * @file app/Http/Middleware/Recaptcha.php
 */

namespace App\Http\Middleware;

use Closure;
use ReCaptcha\ReCaptcha as GoogleRecaptcha;

class Recaptcha
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = (new GoogleRecaptcha(config('recaptchav2.secret')))
            ->verify($request->input('g-recaptcha-response'), $request->ip());

        if (!$response->isSuccess()) {
            return redirect()->back()->with('status', 'Recaptcha failed. Please try again.');
        }

        return $next($request);
    }
}
Enter fullscreen mode Exit fullscreen mode

List this new middleware in /app/Http/Kernel.php:

 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
+'recaptcha' => \App\Http\Middleware\Recaptcha::class,
 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
Enter fullscreen mode Exit fullscreen mode

Then, attach this middleware to your form's submit route.

 Route::post('/my-form-action', [
     MyFormController::class, 
     'submitForm'
-]);
+])
+    ->middleware('recaptcha');
Enter fullscreen mode Exit fullscreen mode

(Vue) Pass the reCAPTCHA response

The reCAPTCHA backend expects a token response from the front-end.

-    <vue-recaptcha 
-      :sitekey="$page.props.recaptchav2_sitekey" 
+      @verify="onCaptchaVerify"
-    />
 ...
 methods: {
+  onCaptchaVerify(token) {
+    // Provide the token response to the form object.
+    this.form['g-recaptcha-response'] = token;
   },
Enter fullscreen mode Exit fullscreen mode

Note: This assumes you are using a form data object.

At this point, you can verify you are human and submit the form. If you fail to check the CAPTCHA box, submitting the form should redirect you back with a Recaptcha failed. Please try again. status.

3. Error Handling

On submit, if the reCAPTCHA has failed or has not been attempted, we want to indicate that to the visitor.

Image description

Make the following additions:

 <vue-recaptcha
   :sitekey="$page.props.recaptchav2_sitekey"
   @verify="onCaptchaVerify"
 />
+<span
+  v-if="recaptcha_error"
+  class="error-message"
+>
+  Please confirm you are not a robot.
+</span>
 ...
   data() {
     return {
+      recaptcha_error: false,
       form: {
         ...
       }
     }
   },
   methods: {
     onSubmit() {
+      if (!this.form['g-recaptcha-response']) {
+        // Show error after submit.
+        this.recaptcha_error = true;
+      } else {
+        // Proceed with submission.
+      }
     }
Enter fullscreen mode Exit fullscreen mode

Enjoy your functioning reCAPTCHA.

Discussion (0)