Simple captcha in Laravel

by Vincy. Last modified on February 13th, 2024.

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.

captcha in laravel

Steps to add a captcha in Laravel

  1. Integrate the captcha service provider by installing the Laravel package.
  2. Create application controllers and routes to show an interactive form.
  3. Embed the simple captcha element and hook the package handle to render the captcha to the view.

1. How to integrate the Captcha service provider by installing the Laravel package?

  1. Integrate the Laravel package via composer to install the captcha service provider.
  2. Configure Laravel App to return the providers and aliases.
  3. Add captcha config to the app environment file.
  4. Publish Laravel captcha package.
  5. Add .env constants to the captcha settings.

i) Run the composer command to install the captcha provider

composer require mews/captcha

ii) Configure Laravel App to set the package provider and facade

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,
    ]

iii) Add captcha config to the Laravel .env file

These 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.

iv) Publish Laravel captcha package vendor to the application root

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.

v) Add .env constants to the captcha settings

It 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),
];

Create application controllers and routes to show an interactive form

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');
        }
    }
}

Routes

The below URL rules define the Laravel application routes for directing to the appropriate handle. It is for performing the following requests.

  • Captcha validation.
  • Refreshing captcha to show a new code to the browser.

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']);

Make a Laravel Rule for captcha code validation

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);
    }
}

Email subscription form template

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>

Download

Leave a Reply

Your email address will not be published. Required fields are marked *

↑ Back to Top

Share this page