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
...
/**
* 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! 🍻

Leave a Reply

avatar
  Subscribe  
Notify of