4 min read
How to Set Up a Simple Image Captcha in Laravel

Requirements

  • Laravel version 11
  • PHP version 8.1 or higher
  • Mbstring PHP Extension
  • Image Processing PHP Extension (GD Image or Imagick)

Installing Captcha Package

To integrate the Intervention Image library for captcha generation, execute the following command:

composer require intervention/image

Downloading Captcha Font

For our captcha text, we’ll utilize the Open Sans font. Download the font from Google Fonts and place it in the resources/fonts directory of your Laravel project.

Captcha Generation

Let’s delve into the implementation of captcha generation logic. In this section of the code, we generate a random captcha consisting of a four-digit number. We then use the Intervention Image library to create a captcha image with the generated number. The image is rendered with a white background and the Open Sans Medium Italic font. The font size is randomized between 17 and 20 pixels, and the captcha text is centered both horizontally and vertically within the image. Finally, the captcha value is stored in the session for validation purposes.

// File: app/Http/Controllers/CaptchaController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;

class CaptchaController extends Controller
{
    /**
     * Generate and return a captcha image.
     *
     * @return \Illuminate\Http\Response
     */
    public function getCaptcha()
    {
         // Generate a random captcha (four-digit number)
        $captcha = rand(1000, 9999);

        // Create an instance of ImageManager
        $manager = new ImageManager(new Driver());

        // Create a canvas for the captcha image
        $image = $manager->create(120, 40);

        // Add the captcha text to the image
        $image->text($captcha, 60, 20, function($font) {
            // Set the font file and size
            $font->file(resource_path('fonts/OpenSans-MediumItalic.ttf'));
            $font->size(rand(17, 20));

            // Center the text horizontally and vertically
            $font->align('center');
            $font->valign('middle');
        });

        // Encode the image as a data URL
        $data = $image->encodeByMediaType('image/png')->toDataUri();

        // Store the captcha value in the session
        Session::put('captcha', $captcha);

        // Return the captcha image as JSON response
        return response()->json([
            'captcha' => $data,
        ]);
    }
}

Captcha Validation

Now, let’s explore the implementation of captcha validation logic. In this part of the code, we validate the user-submitted captcha input. First, we ensure that the captcha input is present and contains at least four characters. Then, we retrieve the stored captcha value from the session. If the stored captcha value is not found or if it does not match the user-submitted captcha input, we return a JSON response indicating that the captcha validation has failed. Otherwise, we return a JSON response indicating that the captcha validation was successful.

// File: app/Http/Controllers/CaptchaController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;

class CaptchaController extends Controller
{
    /**
     * Validate the captcha input.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function validateCaptcha(Request $request)
    {
        // Validate the captcha input
        $request->validate([
            'captcha' => 'required|min:4', // Captcha must be 4 characters long.
        ]);

        // Retrieve the stored captcha value from the session
        $captcha = Session::get('captcha');

        // Check if the captcha value is missing or does not match the user-submitted captcha input
        if (!$captcha || $request->captcha != $captcha) {
            // Return a JSON response indicating that the captcha validation failed
            return response()->json([
                'message' => 'The captcha value is invalid. Please try again.',
            ], 422);
        }

        // Captcha validation passed.
        return response()->json([
            'message' => 'Captcha validation successful!',
        ]);
    }
}

Captcha Routes

// File: routes/web.php

use App\Http\Controllers\CaptchaController;

// Route to handle captcha generation
Route::get('/captcha', [CaptchaController::class, 'getCaptcha'])->name('captcha');

// Route to handle captcha validation
Route::post('/validate-captcha', [CaptchaController::class, 'validateCaptcha'])->name('validate.captcha');

Demo Route

To showcase our captcha functionality, let’s create a demo route:

// File: routes/web.php

Route::get('/demo', function () {
    return view('captcha_demo');
});

Now, let’s create a Blade view named captcha_demo.blade.php in the resources/views directory:

<!-- resources/views/captcha_demo.blade.php -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Captcha Demo</title>
    <!-- Include Tailwind CSS -->
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
    <style>
        .btn-primary {
            background-color: #4CAF50; /* Green */
            color: white;
        }

        .btn-secondary {
            background-color: #008CBA; /* Blue */
            color: white;
        }
    </style>
</head

>
<body class="bg-gray-100 h-screen flex flex-col justify-center items-center">
    <h1 class="text-3xl mb-8">Captcha Demo</h1>
    <img src="#" alt="Captcha Image" class="captcha-image mb-4 rounded-lg shadow-lg">
    <input type="text" id="captcha-input" placeholder="Enter Captcha" class="input-field mb-4 px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:border-blue-500">
    <button onclick="refreshCaptcha()" class="btn-primary mr-2  px-4 py-2 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500">Refresh Captcha</button>
    <button onclick="validateCaptcha()" class="btn-secondary mt-4 px-10 py-2 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500">Validate Captcha</button>

    <script>
        function refreshCaptcha() {
            fetch('/captcha')
                .then(response => response.json())
                .then(data => {
                    document.querySelector('.captcha-image').src = data.captcha;
                });
        }

        function validateCaptcha() {
            const captchaInput = document.getElementById('captcha-input').value;
            fetch('/validate-captcha', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRF-TOKEN': '{{ csrf_token() }}'
                },
                body: JSON.stringify({ captcha: captchaInput })
            })
            .then(response => {
                if (response.ok) {
                    alert('Captcha is valid!');
                } else {
                    alert('Captcha is invalid. Please try again.');
                }
            });
        }

        document.addEventListener('DOMContentLoaded', function () {
            refreshCaptcha();
        });
    </script>
</body>
</html>

To access the demo route showcasing the captcha functionality, visit:

http://localhost/demo

App Demo