$ man installer-devtcha-nextjs
Installer devTcha sur un formulaire Next.js
Une protection anti-robot par preuve de travail, sans cookie, sans service tiers et sans image à déchiffrer. Voici comment elle est branchée sur le formulaire d’audit de ce site.
- Vous avez un formulaire public et vous ne voulez pas de reCAPTCHA.
- Vous tenez à ne rien envoyer à un tiers, pour vos visiteurs comme pour le RGPD.
- Vous cherchez un exemple complet plutôt qu’un bout de code hors contexte.
À faire résoudre un petit calcul au navigateur avant l’envoi : peu coûteux pour une personne, cher pour un robot qui répète.
- Dès qu’un formulaire public déclenche un traitement : envoi d’un message, appel à une API, écriture en base.
- Quand une case à cocher d’un service tiers vous gêne, pour la vie privée ou pour l’accessibilité.
01Le principe, en une phrase
Le serveur fabrique un défi, le navigateur cherche le nombre dont l’empreinte correspond, puis renvoie sa trouvaille avec le formulaire. Le serveur vérifie, et refuse un défi déjà utilisé.
Rien à déchiffrer, aucune case à cocher, aucune requête vers un autre domaine que le vôtre. Le visiteur ne voit qu’une ligne qui se coche toute seule.
La page du projet en propose une démonstration, sur un formulaire d’essai : c’est le moyen le plus rapide de voir ce que ça donne avant d’installer quoi que ce soit.

02Installer et tirer un secret
Le paquet est publié sur npm, sans dépendance, sous licence MIT.
npm install devtcha
# un secret d’au moins 32 caractères, devTcha refuse en deçà
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Le secret va dans les variables d’environnement. Il ne sort jamais du serveur : c’est lui qui signe les défis, et c’est sur lui que repose toute la protection, pas sur le secret du code.
03La route qui délivre un défi
Un appel, un défi. Jamais de cache : un défi rejoué n’en est plus un.
import { createChallenge } from 'devtcha/server';
export default function handler(req, res) {
res.setHeader('Cache-Control', 'no-store');
res.status(200).json(
createChallenge({ secret: process.env.DEVTCHA_SECRET })
);
}04La route qui vérifie
Le jeton arrive avec le formulaire. Tant qu’il n’est pas validé, le traitement n’a pas lieu.
import { verifySolution } from 'devtcha/server';
const verdict = await verifySolution(req.body.devtcha, {
secret: process.env.DEVTCHA_SECRET,
});
if (verdict !== 'ok') {
return res.status(400).json({ message: 'Vérification échouée.' });
}
// ... le traitement protégéLa fonction répond « ok », « invalid », « expired » ou « reused ». Ce dernier cas mérite d’être connu : un même défi ne sert qu’une fois, et rejouer un jeton pourtant valide est refusé.
05Le widget, côté navigateur
Le composant se déclare en important le client. Il retient l’envoi le temps du calcul, puis remplit lui-même un champ caché.
useEffect(() => {
import('devtcha/client');
}, []);
// dans le formulaire
<dev-tcha challengeurl="/api/devtcha" name="devtcha" />06Le piège qui m’a coûté une relecture
Avant la publication du paquet, j’avais écrit les imports avec un commentaire « webpackIgnore », pour que la construction ne se plaigne pas d’un module absent. Une fois le paquet installé, l’avoir laissé aurait été pire que rien.
Next n’embarque pas une dépendance qu’on lui demande d’ignorer. L’import aurait échoué en production, le code serait retombé sur sa protection de secours, et la page aurait continué d’annoncer que devTcha était actif. Une protection qui ment sur elle-même ne protège rien.
- Retirez les « webpackIgnore » dès que la dépendance est réelle.
- Contrôlez la longueur du secret, devTcha en exige trente-deux.
- Décidez ce que fait la route si le secret manque, et dites-le au visiteur.
07Vérifier que ça marche vraiment
Un formulaire qui s’envoie ne prouve rien. Ce qui prouve, c’est qu’un jeton rejoué soit refusé. Sur ce site, le tour complet donne ceci.
jeton valide 200 audit rendu
le même jeton rejoué 400 verification_echouee
jeton inventé 400 verification_echouee
sans jeton 400 verification_echoueePour fabriquer un jeton valide hors navigateur, il suffit de chercher le nombre dont l’empreinte SHA-256 du sel suivi du nombre vaut le défi, puis d’encoder le tout en base64. C’est ce que fait le widget, en arrière-plan.
Ce service est tenu par quelqu’un d’autre : l’inscription et les candidatures se font chez lui.