Opinionated code Laravel code for adding recaptcha on website. Bot protection especially on open form (without user authentication)
- Create blade component for ease of reuse
resources/views/components/input/captcha.blade.php
@props([
'id',
'name',
'action'
])
{{--
<x-input.captcha
name="captcha"
id="captcha_user"
action="user_create"
/>
--}}
<script>
(function () {
const id = @json($id);
const captchaKey = @json(config('services.google.captcha.public_key'));
const action = @json($action);
const onLoaded = () => {
console.log('recaptcha on loaded')
if (!window.grecaptcha) {
console.error('Google catcha not loaded. Aborting...')
return;
}
grecaptcha.ready(function() {
console.log('grecaptcha ready')
grecaptcha.execute(captchaKey, { action }).then(function(token) {
const captchaInput = document.querySelector('#' + id) || {}
captchaInput.value = token
console.log({token})
});
});
};
const resource = 'https://www.google.com/recaptcha/api.js?render=' + captchaKey;
const container = document.head;
let script = container.querySelector(`script[data-captcha=google]`);
if (script) {
onLoaded()
return;
}
script = document.createElement('script')
script.src = resource
script.setAttribute('data-captcha', 'google')
script.addEventListener('load', onLoaded)
container.appendChild(script)
if (document.readyState === 'complete') {
onLoaded()
} else {
document.addEventListener('DOMContentLoaded', onLoaded)
}
})()
</script>
<input type="hidden" id="{{ $id }}" name="{{ $name }}">
- Add to form markup
<form action="{{ route('user.store') }}" class="form" method="post">
@csrf
...
<x-input.captcha
name="captcha"
id="captcha_user"
action="user_create"
/>
</form>
- Add form request
// ContactRequest.php
public function rules(): array
{
return [
// ... your other fields
'captcha_user' => [
'required',
]
];
}
public function messages(): array
{
return [
'captcha_user' => __('validation.captcha')
];
}
public function validateCaptcha(): bool
{
return app(CaptchaClient::class)->verify(
$this->input('captcha'),
$this->input('captcha_action')
);
}
- Add google recaptcha client
// CaptchaClient
<?php
namespace Neptune\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\ValidationException;
class CaptchaClient
{
private string $secret;
/**
* CaptchaService constructor.
* @param $secret
*/
public function __construct($secret)
{
$this->secret = $secret;
}
public function verify($action, $input)
{
$response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
'secret' => $this->secret,
'response' => $input,
])
->json();
$isSuccess = $response['success'];
if (! $response['success']) {
throw ValidationException::withMessages([
'captcha' => __('validation.captcha', ['reason' => $response['error-codes'][0]]),
]);
}
if ($response['action'] != $action) {
throw ValidationException::withMessages([
'captcha' => __('validation.captcha', ['reason' => 'action mismatch']),
]);
}
if ($response['score'] < .8) {
throw ValidationException::withMessages([
'captcha' => __('validation.captcha', ['reason' => 'score did not pass minimum mark']),
]);
}
}
}
- Add handler code in controller
// Controller
public function store (ContactRequest $request) {
$request->validateCaptcha();
// further logic
}
All good now your form should resist bots a bit better