Aller au contenu
← 1001 Widgets
N° 049 · ●●●

Formulaire anti-robots

Un formulaire de contact qui repère les robots sans captcha : champ piège, piège de temps, validation et limite d'envois, comme le contact de ce site. Le bon exercice pour comprendre pourquoi la vraie protection est côté serveur.

JSserveur
Ce qu'on apprend
  • Cacher un champ piège que seuls les robots remplissent
  • Rejeter les envois trop rapides pour un humain
  • Limiter le nombre d'envois par adresse côté serveur
Pourquoi ce langage

Pourquoi un formulaire anti-robots ? Mettre un formulaire en ligne, c'est recevoir des dizaines de messages publicitaires par jour. Les captchas agacent les vrais visiteurs. Il existe des pièges plus discrets.

Le champ piège est caché aux humains : un robot qui remplit tout le remplit aussi, et se trahit. Le piège de temps rejette les envois faits en moins de trois secondes : personne n'écrit un message aussi vite. La limite d'envois bloque les rafales venant d'une même adresse.

Toutes ces vérifications doivent tourner sur le serveur : un robot n'exécute pas forcément le JavaScript de la page. La démo les simule dans le navigateur pour qu'on les voie. Et l'adresse qui reçoit les messages reste dans une variable d'environnement, jamais dans la page.

Pourquoi en JavaScript simple ? Côté navigateur, une validation qui aide l'humain. Côté serveur, quelques if qui protègent vraiment.

Comment ça marche
  1. LE CHAMP PIÈGE

    Hors de l'écran, sauté au clavier, ignoré des lecteurs d'écran. Seuls les robots le remplissent.

    <div class="piege" aria-hidden="true">
      <label for="site">Ne pas remplir</label>
      <input id="site" name="site" tabindex="-1" autocomplete="off">
    </div>
    <input type="hidden" name="ouvert" value="">   <!-- l'heure d'ouverture, remplie au chargement -->
  2. CÔTÉ SERVEUR : UNE ROUTE NEXT.JS

    app/api/contact/route.ts : c'est ici que tout se décide.

    const envois = new Map();                    // adresse IP vers liste d'horaires
    export async function POST(req) {
      const d = await req.formData();
      const ip = req.headers.get('x-forwarded-for') ?? 'inconnue';
  3. LES PIÈGES

    On répond « ok » au robot : inutile de lui dire ce qui l'a trahi.

      if (d.get('site')) return Response.json({ ok: true });
      if (Date.now() - Number(d.get('ouvert')) < 3000) return Response.json({ ok: true });
  4. LA LIMITE D'ENVOIS

    Trois messages par minute et par adresse, pas plus.

      const recents = (envois.get(ip) ?? []).filter(t => Date.now() - t < 60000);
      if (recents.length >= 3) return Response.json({ erreur: 'Trop d\'envois' }, { status: 429 });
      envois.set(ip, [...recents, Date.now()]);
  5. VALIDER À NOUVEAU, PUIS ENVOYER

    Le navigateur a déjà vérifié, mais on ne fait jamais confiance au client.

      const mail = String(d.get('mail') ?? '');
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(mail)) return Response.json({ erreur: 'E-mail invalide' }, { status: 400 });
      await envoyerMail({ a: process.env.CONTACT_TO, de: mail, texte: String(d.get('msg')).slice(0, 2000) });
      return Response.json({ ok: true });
    }
Pièges

PIÈGE 1 : Ne vérifier que dans le navigateur

Piège

Mauvais : bloquer les robots avec du JavaScript dans la page. Un robot envoie directement la requête au serveur, sans jamais l'exécuter.

Fix

Correct : refaire chaque contrôle sur le serveur. Le navigateur ne sert qu'à aider l'humain à corriger.

PIÈGE 2 : Cacher le piège avec display: none

Piège

Mauvais : un champ piège en display: none. Les robots les plus simples savent l'ignorer.

Fix

Correct : le sortir de l'écran en CSS, avec aria-hidden et tabindex="-1" pour ne gêner personne.

PIÈGE 3 : Afficher l'adresse de réception

Piège

Mauvais : mettre l'e-mail dans un mailto ou dans le code de la page. Il sera aspiré et inondé de spam.

Fix

Correct : l'adresse vit dans une variable d'environnement du serveur, et le formulaire passe par une route.

PERSONNALISER
  • Un message de confirmation envoyé au visiteur, sans jamais révéler l'adresse de réception.
  • Un filtre de mots interdits, ou un nombre maximal de liens par message.
  • Turnstile de Cloudflare, invisible pour la plupart des visiteurs, si les pièges ne suffisent plus.
OPTIMISATION
  • En production sur plusieurs serveurs, gardez les compteurs dans un stockage partagé comme Redis ou une table de base de données, pas en mémoire.
  • Signez l'heure d'ouverture côté serveur : un robot ne pourra pas la falsifier.
VERS REACT

Avec Next.js, une Server Action peut recevoir le formulaire directement et faire les mêmes vérifications.

L'état du formulaire, les erreurs et le message de succès passent par useActionState.

Les messages d'erreur sont reliés aux champs avec aria-describedby et aria-invalid.

Commentaires
0/1000

Chargement des commentaires…