Almost every Laravel application has some kind of contact form pubically available. Keeping these forms easy to submit is a requirement for your users. The problem is, you’ll be bombarded with SPAM emails multiple times a day.

Solutions like ReCAPTCHA can prevent this altogether, but they come with their own tradeoffs and can be annoying to the user. I want to show you a native Laravel way to stop most SPAM bots from submitting through your form.

The Honeypot

We are going to be using a catch technique using a “honeypot”. This just means we are going to put some code in our app specifically designed to watch for SPAM bots.

Most SPAM bots will go to a form and fill out all the fields available. If there is a series of checkboxes, the bots are designed to check all fields. We’re going to exploit this by making a hidden form field that we’ll watch for on submission.

Create a simple contact form. I’ll be using Bootstrap for the HTML classes, but the markup doesn’t really matter.

<form action="/contact" method="POST">
  @csrf
  <div class="form-group">
    <label for="name">Full Name</label>
    <input
      type="text"
      class="form-control"
      id="name"
      name="name"
      placeholder="Enter your full name"
    />
  </div>
  <div class="form-group">
    <label for="email">Email address</label>
    <input
      type="email"
      class="form-control"
      id="email"
      name="email"
      placeholder="Enter email"
    />
  </div>
  <div class="form-group">
    <label for="comments">Comments</label>
    <textarea
      class="form-control"
      id="comments"
      rows="3"
      name="comments"
    ></textarea>
  </div>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

You would have this submit to a ContactController with a some kind of method on it like store.

Route::post('contact', '[email protected]');

Create a ContactController and add the store method on it. You want to fire an event that sends an email to yourself, or adds it to your company's ticket system. Whatever your use case is, your code will probably look something like this:

/**
 * Get the contact data and email it to ourselves
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function store(Request $request)
{
    $formData = $request->validate([
		'fullname' => 'required',
		'email'    => 'required|email',
		'comments' => 'required'
	]);
	event(new ContactFormSubmitted($formData);
    return redirect()->back()
      ->withSuccess('Your form has been submitted');
}

This code will work well for the humans and your T-1000 series terminators, but will send all that form SPAM flooding your way.

Accept Faxes Only

The trick we want to do is add a form field to our site that should never be checked. I like to name the field “Contact me by fax only”. No human user is ever going to have that checked.

Add this code into your form:

<div class="form-group" style="display: none;">
  <label for="faxonly">
    Fax Only
    <input type="checkbox" name="faxonly" id="faxonly" />
  </label>
</div>

Make sure not to omit the style="display: none;" attribute. We don’t want this field ever shown to users.

Your next instinct may be to add this field to your validation to make sure it is blank. While that would work, you would be informing the bot that their submissions are not getting through. This may result in them tweaking the bot to work with your form.

There is one big rule when trying to defeat spammers and bots. Make them think they are winning. You want the form submission to appear to go through and return a successful response.

Add a condition in your Controller that checks the existence for the faxonly field.

if ($request->faxonly) {
  return redirect()->back()
    ->withSuccess('Your form has been submitted');
}

If the input field is checked, the form will just return a response without doing anything. Good Guys - 1. Spammers - 0.

One last thing to clean up is the duplication we’ve added. We are returning the same success response back to the view. We will DRY up our code by adding a protected function on your Controller that contains the response you’ll always send back.

/**
 * The response to always send back to the frontend
 *
 * @return \Illuminate\Http\Response
 */
protected function formResponse()
{
    return redirect()->back()
      ->withSuccess('Your form has been submitted');
}

Now update your store method to use the new formResponse()

public function store(Request $request)
{
    if ($request->faxonly) {
        return $this->formResponse();
    }
    event(new ContactFormSubmitted($request->all()));
    return $this->formResponse();
}

At this point you should be stopping most SPAM submissions. You can trick this out even more by adding a bunch of JavaScript to the HTML form. My personal favourite is to make the input required and checked by default and uncheck it with JavsScript after page load.

I’m sticking with the solution above as it’s a nice JavaScript free way to stop the bots. If you have any thoughts or ideas about how to make this better, please let me know.