CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) is a common security feature on websites. It is used to differentiate between human users and bots, helping to protect your web applications from automated attacks and spam. Laravel, a popular PHP framework, provides a straightforward way to integrate CAPTCHA into your applications. In this blog, we'll discuss how to make CAPTCHA dynamic in Laravel, which enhances security and user experience.
If you don't know how to use captcha in laravel please check our previous blog How to Add Google No Captcha into PHP Laravel
php artisan make:model Setting -mcr
By this command you can create model, migration, controller and resources
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('settings', function (Blueprint $table) {
$table->id();
$table->json('captcha')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('settings');
}
};
Migrate table
php artisan migrate
Add Following code in the model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model
{
use HasFactory;
protected $fillable = [
'captcha',
];
protected $casts = [
'captcha' => 'array',
];
}
Here $casts is used because we are inserting the data of the captcha in the json format if we use $casts in the model then it automatically encode and decode the json data while inserting and featching data if we will not use this then we have to write encode and decode manually to prevent this we use $casts.
App\Http\Controllers\SettingController
<?php
namespace App\Http\Controllers;
use App\Models\Setting;
use Illuminate\Http\Request;
use File;
class SettingController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$setting = Setting::first();
return view('backend.setting.index',compact('setting'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(Setting $setting)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Setting $setting)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Setting $setting)
{
$data = $request->validate([
'captcha.*' => 'nullable|string',
]);
$setting->update($data);
return back()->with('success','Data Updated Successfully');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Setting $setting)
{
//
}
}
Here we are updating the data where id is 1 means the first occurence in the settings table
@extends('adminlte::page')
@section('header', 'Setting')
@section('content_header')
@stop
@section('content')
<section class="content">
<div class="container-fluid">
<div class="row">
<!-- /.col -->
<div class="col-md-9">
<div class="card">
<form action="{{ route('setting.update', $setting->id) }}" method="post"
enctype="multipart/form-data">
@csrf
@method('PATCH')
<div class="card-body">
<h4 class="pb-3" style="font-weight: 600">Google Captcha</h4>
<div class="form-group row">
<label for="" class="col-sm-2 col-form-label">Site Key</label>
<div class="col-sm-10">
<input type="text" name="captcha[site_key]" id="" class="form-control"
placeholder="Enter Site Key" value="{{ $setting->captcha['site_key'] ?? '' }}">
@error('captcha[site_key]')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="" class="col-sm-2 col-form-label">Secret Key</label>
<div class="col-sm-10">
<input type="text" name="captcha[secret_key]" id="" class="form-control"
placeholder="Enter Secret Key" value="{{ $setting->captcha['secret_key'] ?? '' }}">
@error('captcha[secret_key]')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
</div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="hidden" name="captcha[contact_form]" value="0">
<!-- Default value when unchecked -->
<input type="checkbox" class="custom-control-input" id="contact_form"
name="captcha[contact_form]" value="1" @if ($setting->captcha['contact_form'] ==
true) checked @endif>
<label class="custom-control-label" for="contact_form">Disable/Enable
Captcha</label>
<small> disable/enable the captcha for the form on the
website</small>
</div>
</div>
</div>
<!-- /.tab-content -->
<div class="form-group row">
<div class="offset-sm-2 col-sm-10">
<button onclick="return confirm('Are you sure you want to update profile?');"
type="submit" class="btn btn-danger">Update</button>
</div>
</div>
</form>
</div><!-- /.card-body -->
</div>
<!-- /.card -->
</div>
<!-- /.row -->
</div><!-- /.container-fluid -->
</section>
@stop
Here I have taken the Site key, secret key and the Contact form captcha active or deactivate
After this you can insert the data in the table and can update the data but still it is not dynamic to control the captcha on the website
As you know if you have read our previous blogs of captcha we have to set the Site and Secret Key in the .env so here we are not going to pass the data in the .env file and we are going to use it directly
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Models\Setting;
use View;
use Config;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
$settingData = Setting::whereId('1')->firstOrFail();
Config::set('captcha.sitekey', $settingData->captcha['site_key']);
Config::set('captcha.secret', $settingData->captcha['secret_key']);
Config::set('custom.forms.contactform', $settingData->captcha['contact_form'];
Config::set('custom.forms.loginform', $settingData->captcha['loginform']);
}
}
Here we are passing the data form the table to the captcha.php file located in the config folder so here we are accessing the sitekey located in the config/captcha.php and give the $settingData->captcha['site_key'] to sitekey and $settingData->captcha['secret_key'] to secret.
Now you can use this as dynamic and can change the keys dynamically using the admin panel without going in the code
Now to activate and dactivate the captcha in the contact form we have inserted a toggle data 1 or 0 but using toggle button you can check it in the blade code to use that simple
Create the custom.php file in config folder and add this code there.
<?php
return [
'forms' => [
'contactform',
'loginform'
],
];
Here you create your own keys which you can use for further uses.
Now use in blade
<form method="post" action="{{ route('contact.mail') }}">
@csrf
<div class="row">
<div class="col-md-6 mb-3">
<input type="text" name="name" class="form-control mb-0 @error('name') is-invalid @enderror"
value="{{ old('name') }}" placeholder="Your Name:">
@error('name')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="col-md-6 mb-3">
<input type="tel" value="{{ old('phone') }}" class="form-control mb-0 @error('phone') is-invalid @enderror"
name="phone" placeholder="Telephone:">
@error('phone')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="col-md-12 mb-3">
<input type="email" value="{{ old('email') }}"
class="form-control mb-0 @error('email') is-invalid @enderror" name="email" placeholder="Email:">
@error('email')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="col-md-12 mb-3">
<textarea name="message" rows="7" class="mb-0" placeholder="Message:">{{ old('message') }}</textarea>
@error('message')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="col-md-12 mb-3">
@if (config('custom.form.contactform') == 1)
<div class="mb-0">
{!! NoCaptcha::renderJs() !!}
{!! NoCaptcha::display() !!}
</div>
@error('g-recaptcha-response')
<span class="text-danger">{{ $message }}</span>
@enderror
@endif
</div>
<div class="col-md-12">
<button type="submit" class="butn">Submit <i
class="fas fa-long-arrow-alt-right margin-10px-left"></i></button>
</div>
Here if the $settingData->captcha['contact_form'] is 1 in the database table then the captcha will visible on the website if it is 0 will not visible on the website.
Similarly you can use this for login page
<form method="post" action="{{ route('contact.mail') }}">
@csrf
<div class="row">
<div class="col-md-12 mb-3">
<input type="email" value="{{ old('email') }}"
class="form-control mb-0 @error('email') is-invalid @enderror" name="email" placeholder="Email:">
@error('email')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="col-md-6 mb-3">
<input type="pasword" value="" class="form-control mb-0 @error('password') is-invalid @enderror"
name="password" placeholder="Password">
@error('phone')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="col-md-12 mb-3">
@if (config('custom.form.loginform') == 1)
<div class="mb-0">
{!! NoCaptcha::renderJs() !!}
{!! NoCaptcha::display() !!}
</div>
@error('g-recaptcha-response')
<span class="text-danger">{{ $message }}</span>
@enderror
@endif
</div>
<div class="col-md-12">
<button type="submit" class="butn">Submit <i
class="fas fa-long-arrow-alt-right margin-10px-left"></i></button>
</div>
</form>
Now let’s add the login into controller for login:
protected function validateLogin(Request $request)
{
if (config('custom.forms.loginform') == 1)
{
$request->validate([
'email' => 'required',
'password' => 'required',
'g-recaptcha-response' => 'required|captcha'
],
[
'g-recaptcha-response' => [
'required' => 'Please verify that you are not a robot.',
'captcha' => 'Captcha error! try again later or contact site admin.',
]
],
);
}else{
$request->validate([
'email' => 'required',
'password' => 'required',
]);
}
}
And similarly you can use this for any forms and you can use similarly this " config('custom.form.loginform') == 1 " in controller logic if condition