Adding reCaptcha to a Laravel form

Last updated

Antonio Ufano avatar

Antonio Ufano

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 site in reCaptcha and get your site and secret keys.
  • Add the keys to the environment of our app (.env file in Laravel).
  • Include the reCaptcha input in our page form.
  • Add the reCaptcha validation in our back end (a controller method in Laravel).

Register your website for reCaptcha

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 the reCaptcha 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 in the backend

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.

  • A request is sent to the Google servers in order to do the verification. This requests contains our site key so the verification is linked to our site.
  • Google server responds with a unique id, which is included in our form in an input with the name "g-recaptcha-response". Once the form is submitted, we'll be able to access the "g-recaptcha-response" to obtain the verification id.

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

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!

If you enjoyed this article consider sharing it on social media or buying me a coffee ✌️

Oh! and don't forget to follow me on Twitter where I share tons of dev tips 🤙

Other articles that might help you

my projects

Apart from writing articles in this blog, I spent most of my time working on my personal projects.

lifeboard.app logo

theLIFEBOARD.app

theLIFEBOARD is a weekly planner that helps people achieve their goals, create new habits and avoid burnout. It encourages you to plan and review each week so you can easily identify ways to improve your productivity while keeping track of your progress.

Sign up
soliditytips.com logo

SolidityTips.com

I'm very interested in blockchain, smart contracts and all the possiblilities chains like Ethereum can bring to the web. SolidityTips is a blog in which I share everything I learn about Solidity and Web3 development.

Check it out if you want to learn Solidity
quicktalks.io logo

Quicktalks.io

Quicktalks is a place where indie hackers, makers, creators and entrepreneurs share their knowledge, ideas, lessons learned, failures and tactics they use to build successfull online products and businesses. It'll contain recorded short interviews with indie makers.

Message me to be part of it