Concevoir un formulaire HTML anti spam sans Captcha

Logo

Introduction

Vous venez de développer un formulaire de contact sur vos pages Web pour recevoir des emails d’internautes, et à peine quelques jours plus tard, votre boîte aux lettres est inondée de spams, encore appelés pourriels.

Formulaire de contact

Il existe bien Captcha qui demande à l’internaute de retranscrire les lettres et chiffres d’une image pour s’assurer qu’il ne s’agit pas d’un robot de spam :

Exemple Captcha

cependant pour avoir été contraint plusieurs fois sur différents sites à retranscrire les caractères, vous vous êtes surpris à vous demander si il s’agissait d’une minuscule ou d’une majuscule, si le chiffre était placé avant ou après la lettre, parfois vous devez plisser des yeux pour reconnaître un caractère. Bref, vous ne souhaitez pas mettre en œuvre cette technologie non seulement pour les malvoyants, mais aussi pour les astigmates, hypermetropes… qui n’ont pas leurs lunettes à portée de main.

En glanant ça et là sur le Web : oui, une méthode anti spam existe sans avoir recours à Captcha. Elle est simple et en pur HTML / Javascript. Elle a fait ses preuves, depuis la mise en place de celle-ci, plus aucun spam ! en provenance directe du formulaire.

Code d’origine du formulaire

Le code HTML 5 du formulaire est on ne peut plus basique.

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

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

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

  <cite>* Champs obligatoires</cite>

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

</form>

Code anti spam

Bien entendu, l’attribut action="mailto:nomprenom@domaine.com" doit être banni du formulaire. Cette directive est un nid à spams.

Un robot de spam remplit dans leur majorité tous les champs d’un formulaire, donc la technique anti spam va consister à ajouter un champ dans le formulaire, non visible pour l’internaute, et appliquer quelques astuces pour inciter très fortement le robot à remplir le champ. Dans le traitement des données reçues par le formulaire, si ce champ est rempli alors il s’agit d’un spam.

Dans le code HTML du formulaire, ajouter ce champ "fantôme" (champ remarque ici) :

  <label class="remarque">Remarque</label>
  <input class="remarque" name="remarque" 
            pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$" 
            placeholder="nom@domaine.com">
  • définir les attributs name et/ou id, mais surtout pattern et placeholder, ces deux derniers attributs vont encore plus tromper le robot de spam puisqu’ils ont vocation à améliorer l’ergonomie et la lisibilité pour un vrai internaute. Difficile de dire si l’attribut pattern qui définit les masques de saisie a une incidence bénéfique pour mieux bloquer les spams, la littérature sur le Web n’est pas fournie sur ce point mais il y a fort à parier que les robots de spam intègrent les expressions régulières des masques dans leurs algorithmes.
  • insérer une balise label avant le champ input, les robots savent détecter un champ caché anti spam avec l’absence d’une balise label.
  • pour les attributs name et id du champ, ne pas appliquer de termes qui seraient trop faciles à interpréter comme des champs anti spams par le robot, comme par exemple : hidden, antispam, cache, fantome, ghost… Utiliser des identifiants classiques d’un formulaire : email%, remarque, description, complement… toujours afin d’inciter le robot à remplir le champ.
  • Bannir l’attribut type="hidden" pour cacher le champ et le label à l’internaute, les robots sont en mesure d’interpréter la raison de la présence de cet attribut. Pour cacher le champ et le label, la feuille de style externe pour la classe remarque prend le relai avec la propriété display:none;. Éviter de coder cette propriété de style dans le corps de la page au sein de balises style afin que cela ne soit pas "parsé" et interprété par les robots comme ils le font pour l’attribut type="hidden".
#formcontact > .remarque { display:none; }

Et c’est fini, PHP ou votre langage préféré de développement Web prend le relai : si le champ remarque contient des données, c’est un spam, on s’arrête là pour le traitement des données du formulaire.

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

Le code PHP peut être améliorié si on souhaite conserver une trace des spams avant la commande die, les variables $_POST sont sauvegardées dans un fichier de log en utilisant var_export et 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();
}

Renforcer encore l’anti spam avec Javascript

Javascript multiplie les chances de lutter contre les spams. Pourquoi ? Le volume des pages est énorme sur la toile, aussi la plupart des robots de spam se contentent uniquement de charger la réponse HTTP pour rechercher des formulaires avec des champs à remplir et des éléments submit.

Exemple de réponse HTTP
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="fr">
...
 <body>
  <form id="formcontact" method="POST" accept-charset="UTF-8">
     ...
     <input name="sendmail" required="true" id="sendmail" type="submit" value="Envoyer">
  </form>
 </body>
...
</html>
          

Création dynamique du formulaire après chargement de la page

Lorsque le formulaire est créé dynamiquement en Javascript dans l’arbre DOM de la page HTML après la réponse HTTP, les robots ont encore moins de chance de détecter le formulaire sauf pour les plus évolués d’entre eux, mais ils sont rares. Le code de création du formulaire est plus lourd certes, mais il permet d’échapper à un pourcentage énorme de robots de spams.

Dans l’exemple, le formulaire est ajouté dynamiquement à la fin de la balise article qui est unique dans la page.

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

</article>

Le code Javascript de création dynamique du formulaire avec les méthodes createElement, setAttribute et appendChild est encapsulé dans une fonction appelée display_contact_form (l’intégralité du code de création des balises du formulaire n’est pas retranscrite pour la lisibilité) :

display_contact_form = function() {

  /** Récupération de la balise article */
  v_footer = document.getElementsByTagName('article')[0];

  /** Création du formulaire */
  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');
  
  /** Création du champ Sujet */
  v_input_sujet = document.createElement('input');
  v_input_sujet.setAttribute('name','subject');
  v_input_sujet.setAttribute('required',true);
  v_input_sujet.setAttribute('placeholder','Sujet...');

  /** Ajout du champ sujet dans le formulaire */
  v_form_contact.appendChild(v_input_sujet);
  
  ...
  
  v_input_label = document.createElement('label');
  v_input_label.appendChild(document.createTextNode('Remarque'));
  v_input_label.setAttribute('class','remarque');

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

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

  /** Ajout du formulaire dans la balise article à la fin */
  v_footer.appendChild(v_form_contact);
};

La fonction display_contact_form est invoquée à la fin du chargement de la page avec la méthode addEventListener de l’objet window.

display_contact_form = function() {
  ...
};

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

Cette cinématique est insérée directement dans l’entête de la page (<head>...</head>) ou dans un script javascript (lib.js) qui est chargé en entête de la page.

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

Alternative plus légère avec Javascript

Si le code de création du formulaire semble trop lourd ou surtout si le formulaire est généré par un script PHP non modifiable (plug in d’un logiciel…), ce qui implique dans ce cas de figure la présence du formulaire dans la réponse HTTP, une alternative plus légère, toujours avec Javascript, est possible.

Le champ "caché" anti spam et son label évoqués précédemment sont ajoutés dynamiquement en Javascript dans le formulaire après le chargement de la page. La classe remarque (display:none dans la feuille de style) est bien entendu appliquée sur ces deux éléments pour cacher ces champs à l’internaute.

add_antispamfield = function() {

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

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

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

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

Si le champ remarque est saisi, c’est un spam : les données sont écartées.

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

Si le champ remarque n’existe pas, et dans ce cas le formulaire a été traité par un robot de spam à partir de la réponse HTTP, les données sont également écartées :

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

Une astuce supplémentaire avec IntersectionObserver

Si c’est possible, afficher les formulaires le plus tard possible dans le chargement de la page. Lorsque le formulaire est en bas de page, une autre astuce pour encore mieux protéger le formulaire des robots de spams : les API IntersectionObserver sont très utiles pour n’afficher le formulaire que lorsque la fenêtre d’affichage (viewport) croise l’emplacement du formulaire. Cela réduit encore la probabilité que les robots de spams découvrent le formulaire dans le DOM.

Dans le code exemple, le formulaire de contact n’est plus construit intégralement puis ajouté via l’évènement load.

  • L’élément form est créé et ajouté dans le DOM, mais le formulaire est vide, aucun élément de soumission.
  • La fonction display_contact_form() ne fait à présent qu’ajouter les éléments enfants dans le formulaire (champs de saisie…).
  • En utilisant IntersectionObserver, si le formulaire croise la fenêtre d’affichage (viewport), la fonction display_contact_form() est déclenchée.
/** 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);

Évidemment, cette astuce n’a aucun sens si le formulaire est en haut de la page: dans cette configuration, l’observateur déclencherait immédiatement la fonction display_contact_form().

Conclusion

Pour contrer les robots de spam, leur dissimuler les formulaires par une création dynamique et/ou inviter fortement ces robots à saisir un champ dédié à leur détection, les astuces et techniques HTML 5 / Javascript sont ultra simples, sans avoir à recours à des outils tiers comme Captcha. Plus aucun spam depuis.