Developing anti spam forms without captcha

Logo

Introduction

You’ve just developed a contact form on your web pages to receive emails from internet users, and just a few days later, your mailbox is flooded with spams.

Contact form

There is the tool Captcha which asks the user to type the letters and numbers of an image, this method ensures it is not a spam robot :

Exemple Captcha

However, with these images, you often wonder if it is an uppercase or a lowercase character, if a character is placed before or after another one, and you need sometimes to zoom in for some characters (7 or z ?). So basically you don’t want to setup the captcha technology, especially when thinking of blind or partially sighted people.

Yes ! An anti spam method without using Captcha exists. This method is very easy and only uses the basics : i.e. HTML and Javascript. Since its implementation here, no more spams ! coming from the contact form.

Original code source of the form

The original HTML 5 code for the contact form is very basic.

<form id="formcontact" method="post" accept-charset="UTF-8">
   
  <label>Subject</label>
  <input name="subject" required="true" placeholder="Subject...">

  <label>E-mail</label>
  <input name="email" required="true" 
       pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$" 
       placeholder="name@domain.com">

  <label>Message</label>
  <textarea id="message" name="message" 
       placeholder="Your message" required="true">
  </textarea>

  <cite>* Mandatory fields</cite>

  <input name="sendmail" required="true" id="sendmail" 
       type="submit" value="Send">

</form>

Anti spam code

Obviously, do not use the attribute action="mailto:name@domain.com" in the form. This attribute attracts instantly spam robots.

A spam robot mostly fills all fields in a form. So in the anti spam method, a field, hidden to the user, is added to the form. This field will contain tricks that will invite the spam robots to fill it. When data coming from the form are managed, if the field is filled, then it is a spam, a spam robot filled it.

In the HTML code of the form, add the "ghost" field (here remark) :

  <label class="remark">Remark</label>
  <input class="remark" name="remark" 
            pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$" 
            placeholder="name@domain.com">
  • set the attributes name and/or id, and especially the attributes pattern and placeholder, these two last attributes are specially designed for end users, so the spam robots will surely try to fill the field when encountering these attributes. Hard to say if the attribute pattern has a benefit against the spams, but the most technically advanced spam robots may surely handle properly regular expressions.
  • add a tag label before the field input, it is one more clue for the spam robots that it is a real field for end users.
  • about the attributes name and id of the field, do not use easy identifiers that could help spam robots to identify that the field is not a real one and used for anti spam purposes, for example : hidden, antispam, cache, fantome, ghost… Use regular identifiers for forms : email%, remark, description… we must suggest to the spam robots to fill the field.
  • Do not use the attribute type="hidden" to hide the field and the label to the end users, spam robots now know well why this attribute may be defined against them. To hide the field and the label, use the cascading style sheet. The class remark in the css file sets the display property to none. Try to avoid coding this style property in the page body within style tags, it could be parsed and recognized by spam robots like the attribute type="hidden".
#formcontact > .remark { display:none; }

That’s all , PHP or your preferred language now handles : if the field remark contains data, it is a spam, form data are rejected.

if ($_POST['remark'] != "") { die(); }

The PHP code can be enhanced if we want to log spams before the die command, $_POST variables are saved in a log file using var_export and file_put_contents:


if ($_POST['remark'] != "") {
    $msg  = strftime('%Y-%m-%d %H:%M:%S')."\n";
    $msg .= var_export($_POST,true)."\n";
    file_put_contents('../logs/mailspams.log', $msg, FILE_APPEND | LOCK_EX);
   
    die();
}

Anti spam strengthening with Javascript

Javascript strengthens the struggle against the spams. Why ? Pages volume is huge on the web, so most of the spam robots only read the HTTP response searching for forms with fields to be filled and submit buttons.

HTTP response example
HTTP/1.1 200 OK
Set-Cookie: 60gpBAK=R1224190331; path=/; expires=Tue, 20-Sep-2016 18:30:38 GMT
Date: Tue, 20 Sep 2016 17:09:54 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Set-Cookie: 60gp=R446942833; path=/; expires=Tue, 20-Sep-2016 18:20:59 GMT
Server: Apache
X-Powered-By: PHP/7.0.8
Cache-Control: max-age=0
Expires: Tue, 20 Sep 2016 17:09:54 GMT
Vary: Accept-Encoding

<!DOCTYPE html>
<html lang="en">
…
 <body>
  <form id="formcontact" method="POST" accept-charset="UTF-8">
     …
     <input name="sendmail" required="true" id="sendmail" type="submit" value="Send">
  </form>
 </body>
…
</html>
          

Dynamic creation of the form after loading the page

When the form is built dynamically with Javascript in the DOM tree, so after the HTTP response, spam robots have less chances to detect the form except for the most advanced. The code for creating the form is heavier obviously, but almost all the spam robots have fled when the code is executed.

In this example, the form is dynamically inserted in this example at the end of the tag article which is unique in the page, but the form can be inserted after or before any element, depending on the architecture of your web site.

<article>
  …
  <form id="formcontact" method="POST" accept-charset="UTF-8">
     …
  </form>

</article>

The javascript code creating dynamically the form with the methods createElement, setAttribute and appendChild is encapsulated in the function display_contact_form (the entirety of the code is not given below for greater ease of reading) :

display_contact_form = function() {

  /** Getting the tag article */
  v_footer = document.getElementsByTagName('article')[0];

  /** Form element creation */
  v_form_contact=document.createElement('form');
  v_form_contact.setAttribute('id','formcontact');
  v_form_contact.setAttribute('method','post');
  v_form_contact.setAttribute('accept-charset','UTF-8');
  
  /** Subject field creation */
  v_input_sujet = document.createElement('input');
  v_input_sujet.setAttribute('name','subject');
  v_input_sujet.setAttribute('required',true);
  v_input_sujet.setAttribute('placeholder','Subject...');

  /** Adding the field subject in the form */
  v_form_contact.appendChild(v_input_sujet);
  
  …
  
  v_input_label = document.createElement('label');
  v_input_label.appendChild(document.createTextNode('Remark'));
  v_input_label.setAttribute('class','remark');

  v_input_antispam = document.createElement('input');
  v_input_antispam.setAttribute('name','remark');
  v_input_antispam.setAttribute('pattern','[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$');
  v_input_antispam.setAttribute('placeholder','name@domain.com');
  v_input_antispam.setAttribute('class','remark');

  v_form_contact.appendChild(v_input_label);
  v_form_contact.appendChild(v_input_antispam);
  
  …

  /** Adding the form at the end of the tag article*/
  v_footer.appendChild(v_form_contact);
};

The function display_contact_form is called after the page loading with the method addEventListener of the object window.

display_contact_form = function() {
  …
};

if(window.addEventListener) { 
  window.addEventListener("load",display_contact_form, false); // IE9, Chrome, FireFox
} 

This mechanism is inserted in the header of the page (<head>...</head>) or in a javascript script (lib.js) loaded in the page header.

<head>
…
  <script type="text/javascript" src="../js/lib.js"></script>
…
</head>

Lighter alternative with Javascript

If the code for creating the form is too heavy or if the form creation is managed by a PHP script that can not be modified (software plug in…), a lighter alternative in javascript is still possible.

The anti spam hidden field and its label are added dynamically with Javascript within the form after the page loading. The CSS class remark (display:none) is obviously applied to these 2 elements.

add_antispamfield = function() {

  v_form_contact = document.getElementById('formcontact');
  
  v_input_label = document.createElement('label');
  v_input_label.appendChild(document.createTextNode('Remark'));
  v_input_label.setAttribute('class','remark');

  v_input_antispam = document.createElement('input');
  v_input_antispam.setAttribute('name','remark');
  v_input_antispam.setAttribute('pattern','[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$');
  v_input_antispam.setAttribute('placeholder','name@domain.com');
  v_input_antispam.setAttribute('class','remark');

  v_form_contact.appendChild(v_input_label);
  v_form_contact.appendChild(v_input_antispam);
};

if(window.addEventListener) { 
  window.addEventListener("load",add_antispamfield, false);
} 

If the field remark is not empty, it is a spam : data are discarded.

if ($_POST['remark'] != "") { die(); }

If the field remark does not exist, so in this case the form has been managed by a spam robot from the HTTP response, data are also discarded :

if ( ! isset($_POST['remark']) ) { die(); }

An additional trick using IntersectionObserver

When possible, display forms as late as possible in the page load. When the form is at the bottom of the page, one more trick to better protect the form from spam robots : the IntersectionObserver API are very useful to display the form only when the viewport intersects the form location. It further decreases the likelihood that spam robots discover the form in the DOM.

In the code sample, the contact form is no more fully built and added through the load event.

  • The form element is created and added in the DOM, but the form is empty, no submit elements.
  • The function display_contact_form() now only appends child elements in the form (input fields…).
  • Using IntersectionObserver, if the form intersects the viewport, the function display_contact_form() is fired.
/** Getting the tag article */
  v_footer = document.getElementsByTagName('article')[0];

  /** Form element creation */
  v_form_contact=document.createElement('form');
  v_form_contact.setAttribute('id','formcontact');
  v_form_contact.setAttribute('method','post');
  v_form_contact.setAttribute('accept-charset','UTF-8');
  
  /** Adding the form at the end of the tag article*/
  v_footer.appendChild(v_form_contact);
  
  display_contact_form = function() {
    /** Subject field creation */
    v_input_sujet = document.createElement('input');
    v_input_sujet.setAttribute('name','subject');
    v_input_sujet.setAttribute('required',true);
    v_input_sujet.setAttribute('placeholder','Subject...');
  
    /** Adding the field subject in the form */
    v_form_contact.appendChild(v_input_sujet);
    
    …
    
    v_input_label = document.createElement('label');
    v_input_label.appendChild(document.createTextNode('Remark'));
    v_input_label.setAttribute('class','remark');
  
    v_input_antispam = document.createElement('input');
    v_input_antispam.setAttribute('name','remark');
    v_input_antispam.setAttribute('pattern','[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$');
    v_input_antispam.setAttribute('placeholder','name@domain.com');
    v_input_antispam.setAttribute('class','remark');
  
    v_form_contact.appendChild(v_input_label);
    v_form_contact.appendChild(v_input_antispam);
    
    …
  };

 obsform = new IntersectionObserver(
  (entries, observer) => { entries.forEach( entry => {
    if (entry.isIntersecting===true) {
      display_contact_form();
      obsform.unobserve(entry.target);
    }
  });
 }
 , {root: null, rootMargin: '0px', threshold: 0.1} );
	
 obsform.observe(v_form_contact);

Obviously, this trick has no sense if the form is at the top of the page : in this configuration, the observer would immediately the function display_contact_form().

Conclusion

Against spam robots, hiding forms in the HTTP response or dynamically creating anti spam fields in the forms with Javascript, are efficient enough and very easy to implement. No need of Captcha technologies.

Zero spam here since this method has been implemented.