Software Development

Google Recaptcha v3 on Laravel

Google Recaptcha v3 offers seamless spam protection by not requiring any additional action from user by default (v2 still requires you to click a checkbox).

In this post, I’m sharing a simple way to integrate it with Laravel 6.x built-in authentication flow. The idea is to add custom validation rule that requires a valid Recaptcha token provided (automatically) when user tries to login.

Assuming we have already setup Recaptcha v3 account, we need to add the site key and secret key to our Laravel project. The easisest way is to add new keys in config/app.php:

// config/app.php
...
'recaptcha' => [
	'site_key' => env('RECAPTCHA_SITE_KEY'),
	'secret_key' => env('RECAPTCHA_SECRET_KEY'),
],
...
# .env
...
RECAPTCHA_SITE_KEY="YOUR_SITE_KEY"
RECAPTCHA_SECRET_KEY="YOUR_SECRET_KEY"
...

Next, we need to build the integration part where our application calls Google Recaptcha API to validate user’s token. I choose to build it as a custom Rule by adding a new file app/Rules/RecaptchaV3.php:

<?php
// app/Rules/RecaptchaV3.php
namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class RecaptchaV3 implements Rule
{
    protected $error_codes;

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        // Reference: https://codeforgeek.com/google-recaptcha-v3-tutorial/
        $url = 'https://www.google.com/recaptcha/api/siteverify';
        $data = ['secret' => config('app.recaptcha.secret_key'), 'response' => $value];
        $options = ['http' => [
            'header' => "Content-type: application/x-www-form-urlencoded\r\n",
            'method' => 'POST',
            'content' => http_build_query($data)
        ]];
        $context  = stream_context_create($options);
        $response = file_get_contents($url, false, $context);
        $response_keys = json_decode($response, true);
        if (!$response_keys['success']) {
            $this->error_codes = $response_keys['error-codes'];
        }
        return $response_keys['success'];
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        $msg = 'Recaptcha failed';
        if (!empty($this->error_codes)) {
            $msg = implode(', ', $this->error_codes);
        }
        return $msg;
    }
}

Finally, we can add this rule in our authentication by overriding validateLogin method in app\Http\Controllers\Auth\LoginController.php, and add Recaptcha input in the login view resources/views/auth/login.blade.php.

// app\Http\Controllers\Auth\LoginController.php
use Illuminate\Http\Request;
use App\Rules\RecaptchaV3;
...
/**
* Validate the user login request.
*
* @param  \Illuminate\Http\Request  $request
* @return void
*/
protected function validateLogin(Request $request)
{
	$this->validate($request, [
		    $this->username() => 'required|string',
		    'password' => 'required|string',
		    'g-recaptcha-response' => ['required', new RecaptchaV3],
	]);
}  
...

Make sure to add the g-recaptcha-response input element inside the login form. Otherwise, it won’t get submitted along with the login credentials. You can also put the script below the body as most people do. I put the script exactly below the input just to make it compact.

...
<!-- resources/views/auth/login.blade.php -->
<form method="POST" action="{{ route('login') }}">
...
<!-- recaptcha v3 -->
<input type="hidden" name="g-recaptcha-response">
<script src="https://www.google.com/recaptcha/api.js?render={{ config('app.recaptcha.site_key') }}"></script>
<script>
let siteKey = "{{ config('app.recaptcha.site_key') }}"
grecaptcha.ready(function() {
    grecaptcha.execute(siteKey, {action: 'login'})
        .then(function (token) {
            document.querySelector('input[name=g-recaptcha-response]').value = token
        })
})  
</script> 
</form>
...

To recap, the expected positive authentication flow is like this:

  1. When a user open login page, the Recaptcha script will do its magic to obtains a token (representing the user). Then our script will set the token in a hidden input.
  2. When the user submit the login form, the token will be submitted along with the login credentials
  3. On server side, the RecaptchaV3 validation rule will kick in and send the submitted token to Recaptcha API for validation
  4. Once the token is validated (along with other credentials), the login flow will continue normally

Enjoy! ?

5 3 votes
Article Rating
yohanes.gultom@gmail.com

Share
Published by
yohanes.gultom@gmail.com

Recent Posts

Get Unverified SSL Certificate Expiry Date with Python

Getting verified SSL information with Python (3.x) is very easy. Code examples for it are…

3 years ago

Spring Data Couchbase 4 Multibuckets in Spring Boot 2

By default, Spring Data Couchbase implements single-bucket configuration. In this default implementation, all POJO (Plain…

3 years ago

Firebase Auth Emulator with Python

Last year, Google released Firebase Auth Emulator as a new component in Firebase Emulator. In…

4 years ago

Google OIDC token generation/validation

One of the authentication protocol that is supported by most of Google Cloud services is…

4 years ago

Fast geolocation query with PostGIS

If you need to to add a spatial information querying in your application, PostGIS is…

4 years ago

Auto speech-to-text (Indonesian) with AWS Transcribe and Python

Amazon Web Service Transcribe provides API to automatically convert an audio speech file (mp3/wav) into…

5 years ago