13

I followed recaptcha v3 example and managed to make it return a callback with a score for a page, similar with their demo.

What I don't understand is how to handle the score that is returned.

I understand that the success is based on the treshold. Using the github package the backend verification is returning json (fail or success) back to the frontend.Am I supposed to handle the fail or success in the front end using javascript? What if the browser has the javascript disabled?

I was thinking to use the recaptcha v3 on all the pages and block the users considered bots for an amount of time.

I am using laravel but I can't figure out how to handle the verification in the middleware, or somewhere else, in order to block the users if they don't have a token (javascript is disabled) or they are considered bots.

Victordb
  • 519
  • 1
  • 11
  • 25
  • most people have javascript enabled, and you can use "I'm not a robot" checkbox to determine if they are a robot -- you can integrate the code in the mvc and it should run. just put the code in appropriate places. – unixmiah Sep 14 '18 at 14:51
  • 3
    I believe recaptcha v3 doesn't use the "I'm not a robot" checkbox. And my issue with the disabled javascript is that, if it's disabled, a bot could bypass recaptcha if I handle the success on the frontend using javascript. – Victordb Sep 14 '18 at 14:55
  • have you looked into all php solution? https://developers.google.com/recaptcha/old/docs/php – unixmiah Sep 14 '18 at 14:58
  • Try the Google reCaptcha library, example on https://stackoverflow.com/questions/53582439/recaptcha-v3-cross-browser – bron Dec 02 '18 at 17:15
  • Try the reCaptcha library, see [this post](https://stackoverflow.com/questions/53582439/recaptcha-v3-cross-browser) – bron Dec 02 '18 at 17:18

3 Answers3

4

reCAPTCHA token should be validated server side. First of all, attach generated token into your form:

grecaptcha.ready(function() {
    grecaptcha.execute('{{env('RECAPTCHA_V3_PUBLIC_KEY')}}', {action: 'contactform'}).then(function(token) {
        $('<input>').attr({
            type: 'hidden',
            name: 'g-recaptcha-response',
            value: token
        }).prependTo('.contact-form')
    });
});

Then when you capture the input on you controller, you can use a custom form request:

<?php

namespace App\Http\Requests;

use App\Rules\RecaptchaV3;
use Illuminate\Foundation\Http\FormRequest;

class ContactFormRequest extends FormRequest
{
    public function rules()
    {
        $rules = [
            'name' => 'required',
            'email' => 'required|email',
            'message' => 'required',
            'g-recaptcha-response' => ['required', new RecaptchaV3],
        ];

        return $rules;
    }
...

}

g-recaptcha-response field is required so if users disable JS they will get an error when form input is validated.

Next for g-recaptcha-response we apply a custom validation rule: RecaptchaV3.

Here's my implementation:

<?php

namespace App\Rules;

use GuzzleHttp\Client;
use Illuminate\Contracts\Validation\Rule;

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

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

        $decoded = json_decode($response->getBody());

        return $decoded->success;
    }

    public function message()
    {
        return "You didn't pass reCAPTCHA challenge!";
    }
}

Next, in your controller use the above form request:

public function processContactForm(ContactFormRequest $request)
{
    ...
}

Hope this helps.

Marian
  • 170
  • 1
  • 2
  • 8
1

Unfortunately, recaptcha v3 does not have challenge methods, which means we need to handle the score threshold in our own server side.

The best solution would be that apply both v2 and v3 together, e.g. if v3 fails threshold, then it pops up v2 challenge. The official site suggests to use 2-way authentication e.g. SMS. However, I don't think 70% of people would do it.

I have created a composer package for Laravel framework which supports score settings. You can check the source code in github recaptcha:

You can do score comparison for your own score handler.

The basic usage would be like:

{!!  GoogleReCaptchaV3::requireJs() !!} 
<form method="POST" action="/verify">
@csrf
{!!  GoogleReCaptchaV3::render('contact_us') !!}

<input type="submit" value="submit"> </form>
DA DENG
  • 11
  • 2
0

If JavaScript is disabled, reCAPTCHA doesn't work anyway and most form submissions will/should fail if bot protection is critical to you.

As for the score that V3 returns, it is entirely up to you how you handle it.

Generally this is handled on form validations. The with V3 you could require the g-response value to be greater than 0.8 or something to your liking. Exact implementation varies greatly by how your app is structured.

From the docs: reCAPTCHA v3 returns a score (1.0 is very likely a good interaction, 0.0 is very likely a bot). Based on the score, you can take variable action in the context of your site.

Darryl E. Clarke
  • 7,537
  • 3
  • 26
  • 34
  • Ok but how should I block the form submission if the score failed or the javascript is disabled? Because I get the score back in frontend. Is it right to handle it with javascript I am worrying it could get altered if I handle it in the front end. My issue is that I end up with a score back into frontend of the app and I don't know what to do with it. – Victordb Sep 14 '18 at 15:05
  • 1
    You should be validating the score on the server side. (step 3: Send the token to your backend with the request to verify") - verifying the token on the client side is insecure. – Darryl E. Clarke Sep 14 '18 at 15:07
  • I am validating the token in the backend, but I don't know how to handle when the validation fails. In their example they just send a json back to frontend saying the validation failed. I want to take some action from the backend but their script expects a json response and is supposed to take action on frontend. – Victordb Sep 14 '18 at 15:11