snowsoft/laravel-recaptcha

Simple and painless Google reCAPTCHA package for Laravel framework

v6.0.0 2024-03-16 19:20 UTC

README

Laravel ReCAPTCHA is a very simply-to-use Laravel 5 package to embed Google reCAPTCHA in your application.

Build Status Scrutinizer Code Quality Code Coverage Packagist version Downloads MIT License

What is reCAPTCHA?

Google developers says: "reCAPTCHA protects you against spam and other types of automated abuse. Here, we explain how to add reCAPTCHA to your site or application."

You can find further info at Google reCAPTCHA Developer's Guide

reCAPTCHA available versions

At this moment there are 3 versions available (for web applications):

Get your key first!

First of all you have to create your own API keys here

Follow the instructions and at the end of the process you will find Site key and Secret key. Keep them close..you will need soon!

System requirements

(*) Version 3.6.1 is Laravel 6 ready

Composer

You can install the package via composer:

$ composer require biscolab/laravel-recaptcha

Laravel 5.5 (or greater) uses package auto-discovery, so doesn't require you to manually add the Service Provider, but if you don't use auto-discovery ReCaptchaServiceProvider must be registered in config/app.php:

'providers' => [
    ...
    Biscolab\ReCaptcha\ReCaptchaServiceProvider::class,
];

You can use the facade for shorter code. Add ReCaptcha to your aliases:

'aliases' => [
    ...
    'ReCaptcha' => Biscolab\ReCaptcha\Facades\ReCaptcha::class,
];

Publish package

Create config/recaptcha.php configuration file using the following artisan command:

$ php artisan vendor:publish --provider="Biscolab\ReCaptcha\ReCaptchaServiceProvider"

Set the environment

Add your API Keys

Open .env file and set RECAPTCHA_SITE_KEY and RECAPTCHA_SECRET_KEY:

# in your .env file
RECAPTCHA_SITE_KEY=<YOUR_API_SITE_KEY>
RECAPTCHA_SECRET_KEY=<YOUR_API_SECRET_KEY>
RECAPTCHA_SKIP_IP=<YOUR_IP_LIST>

RECAPTCHA_SKIP_IP (since v5.2.0, not required, CSV format ) allows you to add a list of IP/CIDR (netmask included). It will be the value of skip_ip

The following environment variables have been removed!!! Now only sensitive informations as API keys are allowed as environment variables, that means you have to set configuration values in config/recaptcha.php

  • RECAPTCHA_DEFAULT_VERSION
  • RECAPTCHA_CURL_TIMEOUT
  • RECAPTCHA_DEFAULT_VALIDATION_ROUTE
  • RECAPTCHA_DEFAULT_TOKEN_PARAMETER_NAME
  • RECAPTCHA_DEFAULT_LANGUAGE

Complete configuration

Open config/recaptcha.php configuration file and set version:

return [
    'api_site_key'                  => env('RECAPTCHA_SITE_KEY', ''),
    'api_secret_key'                => env('RECAPTCHA_SECRET_KEY', ''),
    // changed in v4.0.0
    'version'                       => 'v2', // supported: "v3"|"v2"|"invisible"
    // @since v3.4.3 changed in v4.0.0
    'curl_timeout'                  => 10,
    'skip_ip'                       => env('RECAPTCHA_SKIP_IP', []), // array of IP addresses - String: dotted quad format e.g.: "127.0.0.1", IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
    // @since v3.2.0 changed in v4.0.0
    'default_validation_route'      => 'biscolab-recaptcha/validate',
    // @since v3.2.0 changed in v4.0.0
    'default_token_parameter_name' => 'token',
    // @since v3.6.0 changed in v4.0.0
    'default_language'             => null,
    // @since v4.0.0
    'default_form_id'              => 'biscolab-recaptcha-invisible-form', // Only for "invisible" reCAPTCHA
    // @since v4.0.0
    'explicit'                     => false, // true|false
    // @since v4.3.0
    'api_domain'                   => "www.google.com", // default value is "www.google.com"
    // @since v5.1.0
    'empty_message'                => false,
    // @since v5.1.0
    'error_message_key'            => 'validation.recaptcha',
    // @since v4.0.0
    'tag_attributes'               => [
        'theme'                    => 'light', // "light"|"dark"
        'size'                     => 'normal', // "normal"|"compact"
        'tabindex'                 => 0,
        'callback'                 => null, // DO NOT SET "biscolabOnloadCallback"
        'expired-callback'         => null, // DO NOT SET "biscolabOnloadCallback"
        'error-callback'           => null, // DO NOT SET "biscolabOnloadCallback"
    ]
];

(array) tag_attributes

DO NOT SET tag_attributes.callback, tag_attributes.expired-callback, tag_attributes.error-callback to biscolabOnloadCallback. biscolabOnloadCallback is the default JavaScript callback function called when explicit is set to true and widget onload event is fired.

Here you can find further details about tag_attributes.* https://developers.google.com/recaptcha/docs/display#render_param

Reload config cache file

!!! IMPORTANT !!! Every time you change some configuration run the following shell command:

$ php artisan config:cache

Have you updated?

If you are migrating from an older version check your config/recaptcha.php configuration file and compare it with https://github.com/biscolab/laravel-recaptcha/blob/master/config/recaptcha.php.

Make sure config/recaptcha.php is updated

Customize error message

Just for v2 and invisible users.

Before starting please add the validation message to resources/lang/[LANG]/validation.php file

return [
    ...
    'recaptcha' => 'Hey!!! :attribute is wrong!',
];

Embed in Blade

Insert htmlScriptTagJsApi() helper before closing </head> tag.

You can also use ReCaptcha::htmlScriptTagJsApi().

<!DOCTYPE html>
<html>
    <head>
        ...
        {!! htmlScriptTagJsApi($configuration) !!}
    </head>

htmlScriptTagJsApi

htmlScriptTagJsApi function accepts $configuration argument. $configuration has different keys depending on which ReCAPTCHA you are using:

ReCAPTCHA v2 Checkbox

htmlScriptTagJsApi($configuration)

$configuration argument can have following keys:

Form set-up

After you have to insert htmlFormSnippet() helper inside the form where you want to use the field g-recaptcha-response.

You can also use ReCaptcha::htmlFormSnippet() .

<form>
    @csrf

    ...
    {!! htmlFormSnippet() !!}
    <!-- OR -->
    {!! htmlFormSnippet($attributes) !!}
    <input type="submit">
</form>

DO NOT forget @csrf blade directive

htmlFormSnippet([, array $attributes = [] ])

htmlFormSnippet() function does not require attributes but you can override default config data- attributes:

{!! htmlFormSnippet([
    "theme" => "light",
    "size" => "normal",
    "tabindex" => "3",
    "callback" => "callbackFunction",
    "expired-callback" => "expiredCallbackFunction",
    "error-callback" => "errorCallbackFunction",
]) !!}

htmlFormSnippet methos allows are only folowing attribute names:

  • theme
  • size
  • tabindex
  • callback
  • expired-callback
  • error-callback

Any different attribute name will be rejected

Customization

In config/recaptcha.php you can customize reCAPTCHA widget setting tag_attributes array values. Take a look to tag_attributes section in Complete configuration

ReCAPTCHA v2 Invisible

htmlScriptTagJsApi($configuration)

$configuration argument can have following keys:

  • form_id set reCAPTCHA form ID. This will override default_form_id in config/recaptcha.php. This value will be returned by getFormId() function in order to set the form tag id property.

Form set-up

After you have to insert htmlFormButton($button_label, $properties) helper inside the form where you want to use reCAPTCHA.

This function creates submit button therefore you don't have to insert <input type="submit"> or similar.

You can also use ReCaptcha::htmlFormButton($button_label, $properties) .

$button_label is what you want to write on the submit button

<form id="{{ getFormId() }}">
  @csrf ... {!! htmlFormButton($button_label, $properties) !!}
</form>

DO NOT forget @csrf blade directive

getFormId()

getFormId function returns the default form ID value. This is the value of either default_form_id in config/recaptcha.php or $configuration['form_id'] previously set as arguments of htmlScriptTagJsApi helper.

$configuration['form_id'] overrides default settings.

htmlFormButton()

htmlFormButton function accepts 2 arguments:

  • $button_label: (string: optional) the button lable. For example: Subscribe!;
  • $properties: (array: optional) the HTML button properties. For example:
// $properties =
[
    'class' => 'btn btn-info',
    'data-foo' => 'bar'
]

If data-sitekey and data-callback properties are set, they will be overwritten

If class property is set the value g-recaptcha will be appended

Verify submitted data

Add recaptcha to your rules

$validator = Validator::make(request()->all(), [
    ...
    'g-recaptcha-response' => 'recaptcha',
    // OR since v4.0.0
    recaptchaFieldName() => recaptchaRuleName()
]);

// check if validator fails
if($validator->fails()) {
    ...
    $errors = $validator->errors();
}

Embed in Blade

Insert htmlScriptTagJsApi($config) helper before closing </head> tag.

<!DOCTYPE html>
<html>
    <head>
        ...
        {!! htmlScriptTagJsApi([
            'action' => 'homepage',
            'callback_then' => 'callbackThen',
            'callback_catch' => 'callbackCatch'
        ]) !!}

        <!-- OR! -->
        
        {!! htmlScriptTagJsApi([
            'action' => 'homepage',
            'custom_validation' => 'myCustomValidation'
        ]) !!}
    </head>

$config is required and is an associative array containing configuration parameters required for the JavaScript validation handling.

The keys are:

Built-in javascript validation system

As callback of grecaptcha.execute an ajax call to config('recaptcha.default_validation_route') will be performed using fetch function. In case of successful response a Promise object will be received and passed as parameter to the callback_then function you have set. In not set, no actions will be performed.

Same will happen with callback_catch. callback_catch will be called in event of response errors and errors will pass as parameter et that function. If not set, no actions will be performed.

Please, go to Using Fetch for further information on fetch javascript function.

Warning!!! Check browser compatibility fetch function has compatibility issues with some browser like IE. Please create a custom validation function and set custom_validation with its name. That function has to accept as argument the tokenreceived from Google reCAPTCHA API.

Fetch browser compatibility

Validation Laravel route

Default validation route is config('recaptcha.default_validation_route', 'biscolab-recaptcha/validate').
Route and relative Controller are built-in in the package. The route if filtered and protected by Laravel web Middleware, that's why is important you embed csrf-token HTML meta tag and send X-Requested-Wit and X-CSRF-TOKEN headers.

You can also change the validation end-point changing default_validation_route value in recaptcha.php config file.

<head>
    ...
    <!-- IMPORTANT!!! remember CSRF token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">
</head>

Validation response object

The output will be a JSON containing following data:

  • Default output without errors
{
    "action":"homepage",
    "challenge_ts":"2019-01-29T00:42:08Z",
    "hostname":"www.yourdomain.ext",
    "score":0.9,
    "success":true
}
  • Output when calling IP is included in "skip_ip" config whitelist
{
    "skip_by_ip":true,
    "score":0.9,
    "success":true
}

If you embed code in your blade file using htmlScriptTagJsApiV3 helper no validation call will be performed!

More info at Configuration page

  • Output with an empty response from Google reCAPTCHA API
{
    "error":"cURL response empty",
    "score":0.1,
    "success":false
}

In the next paragraph you can learn how handle Validation promise object

"callback_then" and "callback_catch"

After built-in validation you should do something. How? Using callback_then and callback_catch functions.

What you have to do is just create functions and set parameters with their names.

  • callback_then must receive one argument of type Promise.

  • callback_catch must receive one argument of type string

The result should be something like that:

<head>
    ...
    <!-- IMPORTANT!!! remember CSRF token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">
    ...
    <script type="text/javascript">
        function callbackThen(response){
        	// read HTTP status
            console.log(response.status);
            
            // read Promise object
            response.json().then(function(data){
                console.log(data);
            });
        }
        function callbackCatch(error){
            console.error('Error:', error)
        }   
    </script>    
    ...
    {!! htmlScriptTagJsApiV3([
        'action' => 'homepage',
        'callback_then' => 'callbackThen',
        'callback_catch' => 'callbackCatch'
    ]) !!}    
</head>

"custom_validation" function

As just said you can handle validation with your own function. To do that you have to write your function and set custom_validation parameter with its name.

The result should be something like that:

<head>
    ...
    <!-- IMPORTANT!!! remember CSRF token --> 
    <meta name="csrf-token" content="{{ csrf_token() }}">
    ...
    <script type="text/javascript">
        function myCustomValidation(token) {
            // do something with token 
        }
    </script>    
    ...
    {!! htmlScriptTagJsApiV3([
        'action' => 'homepage',
        'custom_validation' => 'myCustomValidation'
    ]) !!}    
</head>