reCaptcha v3 handle score callback

12,368

Solution 1

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.

Solution 2

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>
Share:
12,368
Victordb
Author by

Victordb

Updated on June 08, 2022

Comments

  • Victordb
    Victordb about 2 years

    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
    Victordb almost 6 years
    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.
  • Darryl E. Clarke
    Darryl E. Clarke almost 6 years
    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.
  • Victordb
    Victordb almost 6 years
    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
    Victordb over 5 years
    I ended up with a similar use but I didn't use validation, instead I used if in controller. But I think your solution might be better.
  • Victordb
    Victordb over 5 years
    You could improve the validation rule by using the recaptcha library from github.com/google/recaptcha
  • Robert Mark Bram
    Robert Mark Bram over 5 years
    How do I use the RecaptchaV3 class?
  • Victordb
    Victordb over 5 years
    You use it in validation like this: 'g-recaptcha-response' => ['required', new RecaptchaV3], Do every step in Marian's post in order to work.