This is a quick script to add a simple captcha challenge to protect your web application from spam in Laravel.
The example created for this tutorial uses a captcha service provider by installing the mews/captcha Laravel package.
The below section guides how to add a simple captcha in Laravel with a few steps.
We have already seen a Laravel example for the Google reCaptcha solution with the v2 checkbox challenge. The linked article has the code if you want to integrate the latest reCaptcha v3.
providers
and aliases
..env
constants to the captcha settings.composer require mews/captcha
The code below shows where to configure the installed package to enable the captcha service for the Laravel application.
Open the Laravel application app.php
file found on the root and locate the providers
array. The installed package’s provider class has to be added to this array as shown below.
'providers' => [
...
...
...
Mews\Captcha\CaptchaServiceProvider::class,
]
Also, locate the aliases
section in the same file and set the Captcha Facade
class.
'aliases' => [
...
...
...
'Captcha' => Mews\Captcha\Facades\Captcha::class,
]
.env
fileThese two configurations are used in the package settings. The CAPTCHA_DISABLE is to show or hide the captcha challenge to the front end. It accepts boolean values and the default is false to show the captcha element.
The MATH_ENABLE setting accepts binary 0 or 1. When it is 0, the captcha challenge will be an alphanumeric text. When it is 1, then the view will show the captcha containing math puzzles.
CAPTCHA_DISABLE = false
MATH_ENABLE = 1
We have already created an example to learn how to show a simple alphanumeric captcha using PHP.
This step will create a captcha.php
file the Laravel config
directory. It is to set the defaults or specifications for the captcha provider to show the challenge based on these settings.
php artisan vendor:publish --provider="Mews\Captcha\CaptchaServiceProvider"
It uses the .env
settings to enable a captcha and display a math puzzle to the UI.
.env
constants to the captcha settingsIt shows the default and math settings provided by the package. It uses the Laravel .env
settings to state the disable status and the captcha_type
by this configuration.
config/captcha.php
return [
'disable' => env('CAPTCHA_DISABLE', false),
'characters' => '23456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
'default' => [
'length' => 5,
'width' => 120,
'height' => 36,
'quality' => 90,
'math' => false,
'expire' => 60,
'type' => 'alphanumeric',
],
'math' => [
'length' => 9,
'width' => 120,
'height' => 36,
'quality' => 90,
'math' => true,
'type' => 'flat',
],
//..........
'CAPTCHA_TYPE' => env('MATH_ENABLE', 0),
];
In this step, you have to make a CaptchaController
load UI and process the user data after their interaction.
It handles cases to generate a captcha code on landing. The generateCaptcha()
function returns the captcha code to be displayed on the view on landing.
This Laravel example gives a feature to regenerate the captcha and show a new code to the UI form. The reloadCaptcha()
the below code handles this case.
app/Http/Controllers/CaptchaController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Rules\capchaRule;
class CaptchaController extends Controller
{
public function index()
{
return view('index');
}
public function capthcaFormValidate(Request $request)
{
$request->validate([
'email' => 'required|email',
'captcha' => 'required'
]);
// Validate the captcha
if (!capchaRule::validateCaptcha($request->captcha)) {
return redirect()->back()->withErrors(['captcha' => 'Invalid captcha. Please try again.']);
}
// success message
return redirect()->back()->with('captcha_success', 'Hi, we received your request.');
}
public function reloadCaptcha()
{
$configCaptchaType = config('captcha.CAPTCHA_TYPE');
// Initialize variable to store captcha type
$captchaType = '';
// If the config number is 0, set captcha type to 'flat' (alphanumeric)
// If it's 1, set captcha type to 'math'
if ($configCaptchaType == 0) {
$captchaType = 'alphanumeric';
} else {
$captchaType = 'math';
}
// the generated type will be stored in the captchaImage
$captchaImage = captcha_img($captchaType);
// Return JSON response with the generated captcha image
return response()->json(['captcha' => $captchaImage]);
}
public static function generateCaptcha()
{
$configCaptchaType = config('captcha.CAPTCHA_TYPE');
// If the config number is 0, generate a 'flat' (alphanumeric) captcha,
// otherwise, generate a 'math' captcha
if ($configCaptchaType == 0) {
return captcha_img('alphanumeric');
} else {
return captcha_img('math');
}
}
}
The below URL rules define the Laravel application routes for directing to the appropriate handle. It is for performing the following requests.
routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\CaptchaController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
*/
Route::get('/', [CaptchaController::class, 'index']);
Route::post('/captcha-validation', [CaptchaController::class, 'capthcaFormValidate']);
Route::get('/reload-captcha', [CaptchaController::class, 'reloadCaptcha']);
This PHP class is for implementing the Laravel ValidationRule
class features. It initiates captcha validation by calling the captcha_check()
from a validateCaptcha()
of this Rule
class.
app/Rules/CaptchaRule.php
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class capchaRule implements ValidationRule
{
/**
* Run the validation rule.
*
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
//
}
public static function validateCaptcha($captcha)
{
return captcha_check($captcha);
}
}
This HTML code sets up an email subscription form with a CAPTCHA verification challenge. It collects users’ email addresses. The CAPTCHA challenge prevents spam requests from filtering bots and allow only genuine manual hits.
The captcha can be regenerated by using a refresh button added for this example. It helps to reload the captcha image in case the users are not getting clear the code on the UI.
resources/views/index.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.0-alpha1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<!-- Styles -->
<style>
.container {
max-width: 500px;
}
.reload {
font-family: Lucida Sans Unicode
}
</style>
</head>
<body>
<div class="container mt-5">
<h2 class="text-center">Subscription Form</h2>
@if (session('captcha_success'))
<div class="alert alert-success">
{{ session('captcha_success') }}
</div>
@endif
@if ($errors->any())
@if ($errors->count() > 1)
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@else
<div class="alert alert-danger">
{{ $errors->first() }}
</div>
@endif
@endif
<form method="POST" action="{{ url('captcha-validation') }}">
@csrf
<div class="form-group">
<label>Email</label>
<input type="text" class="form-control" name="email">
</div>
<div class="form-group mt-4 mb-4">
<div class="captcha">
<span>{!! App\Http\Controllers\CaptchaController::generateCaptcha(config('captcha.default.type')) !!}</span>
<button type="button" class="btn btn-danger reload" id="reload">↻</button>
</div>
</div>
<div class="form-group mb-4">
<input id="captcha" type="text" class="form-control" placeholder="Enter Captcha" name="captcha">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block" id="submitBtn">Submit</button>
</div>
</form>
</div>
</body>
<script type="text/javascript">
$('#reload').click(function() {
$.ajax({
type: 'GET',
url: 'reload-captcha',
success: function(data) {
$(".captcha span").html(data.captcha);
}
});
});
</script>
</html>