Back to blog

Adding reCaptcha to a Laravel form

Posted

One of the most annoying things of having a form in a website is seeing it being targeted by spam bots. In this article I'm going to explain how to get rid of them (or at least most of them...) by integrating Google's reCaptcha in our form.

Note: For this example I'll be using a Laravel app but I'll explain the basic concepts of how reCaptcha works and how to integrate it so, if you're using a different framework, you should be able to follow along._

We'll just need to follow a few simple steps:

Register your website

Registering your website to use reCaptcha is pretty straight forward. Just visit this link (logged in with your Google account) and fill the form. For the label, put something that will easily identify your site and for the reCaptcha type, I selected the v2 Checkbox (note that the integration is different depending on the type).

Once registered you'll get a screen with your unique keys and the client/server integration details:

keys

And that's all we have to do from the reCaptcha side. Now we just have to integrate it into our application.

Add keys to your app

We'd need to use our client and server keys to call the reCaptcha API from both the front and back end of our application so in order to have them available in our whole app, we can add the as environment variables or, as my app is built in Laravel, I'll use the .env file adding them at the end of it:

// .env file
GOOGLE_RECAPTCHA_KEY=YOUR_RECAPTCHA_SITE_KEY
GOOGLE_RECAPTCHA_SECRET=YOUR_RECAPTCHA_SECRET_KEY

In JavaScript you can load them as environment variables and access them with process.env.VARIABLE_NAME.

Include reCaptcha input in the form

Next step is to include the reCaptcha checkbox in our form using the two code snippets from the reCaptcha dashboard. For this example I have a contact form with a few inputs (name, email and text) that sends all the payload via a POST request to a route called "contact.send" which is handled by a controller. It looks like this:

<form action="{{route('contact.send')}}" class="mb-3" method="post" enctype="multipart/form-data">
    {!! csrf_field() !!}
    <div class="form-row">
        <div class="form-group col-md-5">
        <label for="name" class="control-label">Name</label>
        <input name="name" class="form-control" type="text" id="name" value="{{old('name')}}">
        </div>
        <div class="form-group col-md-7">
        <label for="email" class="control-label">Email</label>
        <input name="email" class="form-control" type="email" id="email" value="{{old('email')}}">
        </div>
    </div>
    
    <div class="row">
        <div class="col-md-12 form-group">
        <label for="body" class="control-label">Message</label>
        <textarea name="body" id="body" class="form-control" cols="50" rows="10">{{old('body')}}</textarea>
        </div>
    </div>
   
    <div class="form-row mt-2">
        <div class="col-md-2 form-group text-right">
        <button type="submit" class="btn btn-info">Send</button>
        </div>
    </div>
</form>

The scripts provided in the reCaptcha dashboard contain the input that we'll have to add to our form, and the script that is executed when it's triggered. Here's how our previous form looks after I've added the snippets:

<form action="{{route('contact.send')}}" class="mb-3" method="post" enctype="multipart/form-data">
    {!! csrf_field() !!}
    <div class="form-row">
        <div class="form-group col-md-5">
        <label for="name" class="control-label">Name</label>
        <input name="name" class="form-control" type="text" id="name" value="{{old('name')}}">
        </div>
        <div class="form-group col-md-7">
        <label for="email" class="control-label">Email</label>
        <input name="email" class="form-control" type="email" id="email" value="{{old('email')}}">
        </div>
    </div>
    
    <div class="row">
        <div class="col-md-12 form-group">
        <label for="body" class="control-label">Message</label>
        <textarea name="body" id="body" class="form-control" cols="50" rows="10">{{old('body')}}</textarea>
        </div>
    </div>
    @if(env('GOOGLE_RECAPTCHA_KEY'))
    <div class="row">
        <div class="col-md-12 text-right">
        <div class="g-recaptcha" data-sitekey="{{env('GOOGLE_RECAPTCHA_KEY')}}"></div>
        </div>
    </div>
    @endif
    <div class="form-row mt-2">
        <div class="col-md-2 form-group text-right">
        <button type="submit" class="btn btn-info">Send</button>
        </div>
    </div>
</form>

<script src='https://www.google.com/recaptcha/api.js'></script>

Note that the input snippet provided includes our site key hardcoded into it and, as I've included it in the .env file, I've replaced it with a reference to the variable ;) At this point we should be able to see the reCaptcha input when we load our form, but there is an additional step we need to do before it actually works.

Add reCaptcha validation back end

In order to understand the changes that we need to do in our controller, we first need to understand what actually happens when a user clicks on the reCaptcha input.

As part of the server side normal validations in our controller (check that all fields of our form are filled and with the correct format etc...) we'll need to also check if Google has successfully verified that our user is not a bot by sending a POST request to Google's /recaptcha/api/siteverify including our secret key and the verification id from the "g-recaptcha-response" input, as indicated in the reCaptcha documentation:

reCaptcha server integration

In PHP we can do this using the file_get_contents() function to send the POST request, and the json_decode() function to parse the response to a JSON object as follows:

// MessageController.php

/**
    * Store a newly created resource in storage.
    *
    * @param  \Illuminate\Http\Request  $request
    * @return \Illuminate\Http\Response
    */
public function store(Request $request)
{
    // validate all form fields are filled
    $request->validate([
    'name'=> 'required',      
    'email' => 'required|email',
    'body' => 'required'
    ]);

    
    // check if reCaptcha has been validated by Google      
    $secret = env('GOOGLE_RECAPTCHA_SECRET');
    $captchaId = $request->input('g-recaptcha-response');
    
    //sends post request to the URL and tranforms response to JSON
    $responseCaptcha = json_decode(file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret='.$secret.'&response='.$captchaId));
    

    if($responseCaptcha->success == true){
    
    // store the message in database
    $message =  new Message;
    $message->name = $request->name;
    $message->email = $request->email;
    $message->body = $request->body;

    $message->save();

    // prepare notification
    $request->session()->flash('message-for', 'message');                
    $request->session()->flash('message-level', 'alert-success' );
    $request->session()->flash('message-content', 'Thanks for your message ' . $request->name . '. I will reply to you shortly');

    return back();

    }else{
    // send back error message
    $request->session()->flash('message-for', 'message');                
    $request->session()->flash('message-level', 'alert-warning' );
    $request->session()->flash('message-content', 'Looks like you are a replicant. Sorry but I do not receive emails from non human entities :(');
    
    

    return back();
    }
    
}

In JavaScript, you can use axios to send the POST request and then JSON.parse() to transform the response to an object.

The response from our POST request will contain a <em="" style="" data-children-count="0">"success" property with a true/false value which we'll use to return an error message or, as in this example, store the message in our database. Now it should be ready to test.</em="">

Conclusion

For me, adding reCaptcha to the public forms of the websites has become a must if you dont want to have to deal with tons of spam and bots. This article only covers the reCaptcha v2 checkbox, but different types can be integrated in a similar manner. Hope you find this article helpful :)

Happy coding!

Included in categories

coding laravel javascript

Did you enjoy this article? Consider buying me a coffee or sharing it on social media.

Buy me a coffeeBuy me a coffee

Oh! and don't forget to follow me on Twitter 🤙