12k
All articles

Pourquoi vous ne devriez pas valider les e-mails avec des regex

Pourquoi les regex email échouent: elles rejettent des adresses valides, acceptent des adresses non distribuables et peuvent causer un ReDoS. Utilisez plutôt HTML5 ou une bibliothèque.

OpenReplay Team
OpenReplay Team
Pourquoi vous ne devriez pas valider les e-mails avec des regex

Une regex ne peut pas valider une adresse e-mail — elle peut seulement en vérifier la forme approximative, et même le motif le plus soigneusement élaboré rejettera des adresses légitimes tout en acceptant des adresses syntaxiquement plausibles qui ne délivreront jamais rien. La raison n’est pas que vous n’avez pas encore trouvé le bon motif ; c’est que la question « cette adresse e-mail est-elle valide ? » confond trois problèmes distincts, et une regex ne peut en toucher qu’un seul — le moins utile. Cet article sépare ces trois problèmes, cite ce que les spécifications disent réellement (RFC 5321, RFC 5322, RFC 6531, et le WHATWG HTML Living Standard), montre précisément où les motifs populaires échouent dans les deux sens, et vous fournit le code JS/TS pragmatique à utiliser à la place.

Points clés

  • La validation des e-mails comporte trois couches distinctes — une vérification UX de base, une validation syntaxique selon les RFC 5321/5322, et une vérification d’existence — et seul un e-mail de confirmation prouve que l’adresse reçoit effectivement des messages.
  • Le <input type="email"> du HTML Living Standard utilise une regex que la spécification elle-même qualifie de « violation délibérée de la RFC 5322 » ; elle n’est intentionnellement pas conforme à la RFC et constitue un meilleur choix par défaut que tout motif que vous écrirez à la main.
  • Les regex d’e-mail populaires échouent dans les deux sens : elles rejettent de vraies adresses (le plus-addressing, les nouveaux gTLD, les parties locales entre guillemets, les adresses internationalisées selon la RFC 6531) et acceptent des adresses non délivrables.
  • Une regex d’e-mail sujette au backtracking, exécutée côté serveur dans Node.js, peut être exploitée avec une entrée courte soigneusement construite pour bloquer la boucle d’événements — un vecteur de déni de service par ReDoS (CWE-1333).
  • La seule contrainte de longueur qui vaille la peine d’être appliquée avant tout motif provient de la RFC 5321 §4.5.3.1.3 : l’adresse elle-même est limitée à 254 caractères.

Les trois couches de la validation des e-mails

La validation des e-mails comporte trois couches distinctes : une vérification UX de base (cela ressemble-t-il à un e-mail ?), une validation syntaxique (est-ce conforme aux règles des RFC 5321/5322 ?), et une vérification d’existence (cette boîte aux lettres reçoit-elle réellement des messages ?). Seule la troisième couche prouve que l’adresse fonctionne, et seul un e-mail de confirmation le démontre. Les références qui vous disent d’« arrêter d’utiliser des regex » ont raison, mais elles mélangent ces couches. Les distinguer clairement permet de savoir quel outil utiliser à quel endroit.

  • Couche 1 — Vérification UX de base. Une vérification rapide et peu coûteuse, côté client, qui détecte les fautes de frappe évidentes (alicegmail.com, un espace en fin de chaîne) et fournit un retour immédiat. C’est la seule couche où une regex a sa place, et même là, vous voulez le motif le plus minimal qui fasse le travail.
  • Couche 2 — Validation syntaxique. La chaîne est-elle conforme à la grammaire définie dans les RFC sur les e-mails ? C’est bien plus difficile qu’il n’y paraît, cela dépasse les capacités des regex écrites à la main, et — point crucial — cela ne prouve rien quant à la délivrabilité. Une adresse parfaitement conforme aux RFC peut pointer vers un domaine qui n’existe pas.
  • Couche 3 — Vérification d’existence. Une vraie boîte aux lettres reçoit-elle des messages à cette adresse ? La seule preuve qu’une adresse e-mail fonctionne est un message délivré avec succès ; un e-mail de confirmation accomplit en une seule étape ce qu’aucune regex ne peut faire.

L’erreur que fait presque toute « regex e-mail ultime » est de tenter de réaliser la Couche 2 parfaitement, alors que la Couche 2 ne répond pas à la question que tout le monde se pose réellement. La vraie question est celle de la Couche 3, et aucun motif ne l’atteint.

Ce que « valide » signifie réellement

Une adresse e-mail valide est bien plus permissive que la plupart des regex ne le supposent, car la grammaire définie dans RFC 5321 (SMTP) et RFC 5322 (le format des messages) autorise des constructions qui semblent incorrectes. La partie locale — tout ce qui précède le @ — peut contenir une longue liste de caractères spéciaux et peut même être une chaîne entre guillemets.

La partie locale non délimitée est construite à partir de atext, défini dans RFC 5322 §3.2.3, qui autorise ces caractères en plus des lettres et des chiffres :

! # $ % & ' * + - / = ? ^ _ ` { | } ~

Cela signifie que user+tag@example.com (le plus-addressing) est valide — le + est un atext ordinaire, selon RFC 5321 §4.1.2. Que le serveur destinataire traite le +tag comme une sous-adresse est spécifique à l’implémentation (RFC 5233), mais l’adresse elle-même est bien formée. La partie locale peut également être une chaîne entre guillemets : "user name"@example.com est valide selon RFC 5321 §4.1.2 et RFC 5322 §3.2.4, espaces compris. Le domaine peut être une adresse IP littérale entre crochets — user@[192.168.1.1] est valide selon RFC 5321 §4.1.3.

Il existe une contrainte qui vaut la peine d’être appliquée facilement. RFC 5321 §4.5.3.1.3 limite le forward-path à 256 octets, crochets angulaires inclus, ce qui laisse 254 caractères pour l’adresse elle-même ; la partie locale est limitée à 64 octets (§4.5.3.1.1) et le domaine à 255 (§4.5.3.1.2). Une vérification de longueur est la seule validation qu’une comparaison de chaînes gère correctement et qu’une regex n’a pas besoin d’effectuer.

Adresses internationalisées (EAI)

Les adresses e-mail internationalisées définies dans RFC 6531 — comme 用户@例子.广告 — sont valides et de plus en plus courantes ; aucune regex limitée à l’ASCII ne les gère, et c’est un problème de bibliothèque, pas un problème de regex. L’EAI (RFC 6531 §3.3) étend la partie locale pour autoriser l’UTF-8, et le domaine peut être un Unicode non-ASCII. C’est distinct des domaines encodés en punycode IDNA (RFC 5891) : l’EAI couvre également la partie locale. Tout motif qui suppose [a-zA-Z0-9] pour la partie locale est incorrect pour une part croissante des utilisateurs dans le monde, et il n’existe pas de regex unique qui accepte correctement à la fois les parties locales ASCII et Unicode sans accepter également des valeurs incorrectes.

Pourquoi les regex de validation d’e-mail échouent dans les deux sens

Une regex d’e-mail écrite à la main échoue à la fois comme filtre entrant et comme filtre sortant : elle produit des faux négatifs (en rejetant des adresses délivrables) et des faux positifs (en acceptant des adresses conformes à la grammaire mais qui ne recevront jamais de message). Ces deux modes d’échec se retrouvent constamment en production parce que les suites de tests utilisent test@example.com, qui passe tous les motifs.

Prenons le classique copié-collé de Stack Overflow — un motif qui exige un TLD de 2 à 4 caractères :

// Un motif couramment copié-collé. Ne pas utiliser.
const bad = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;

Voici ce qu’il fait avec de vraies adresses :

AdresseCe qu’elle testeCette regexRésultat correctPourquoi c’est incorrect
name+filter@gmail.complus-addressing✅ acceptée✅ valide(passe ici, mais les motifs plus stricts rejettent +)
user@studio.photographygTLD long❌ rejetée✅ valide{2,4} rejette les TLD de plus de 4 caractères
"user name"@example.compartie locale entre guillemets❌ rejetée✅ valideles chaînes entre guillemets et les espaces sont valides
用户@例子.广告EAI (RFC 6531)❌ rejetée✅ valideclasses de caractères ASCII uniquement
someone@validformat.testdomaine inexistant✅ acceptée❌ non délivrablela syntaxe est correcte ; le domaine ne se résout pas

Une regex qui rejette name+filter@gmail.com (le plus-addressing) ou user@studio.photography (un gTLD délégué dans le cadre du New gTLD Program de l’ICANN, .photography ayant été ajouté à la racine en 2013) n’est pas stricte — elle est simplement dans l’erreur. Ce sont des adresses syntaxiquement plausibles utilisant des fonctionnalités e-mail valides. La contrainte {2,4} sur le TLD à elle seule casse .photography, .accountants, .engineering, et des centaines d’autres délégations valides.

Les enregistrements de session révèlent fréquemment des utilisateurs confrontés à des erreurs de validation, qui corrigent leur saisie plusieurs fois avant d’abandonner le formulaire. Des études sur l’utilisabilité des formulaires ont régulièrement identifié les frictions liées à la validation comme un facteur contribuant à l’abandon et à la baisse des taux de conversion.

Les faux positifs — des adresses qui passent la regex mais ne délivrent jamais rien — sont tout aussi réels. someone@validformat.test passe le motif ci-dessus et la plupart des autres, pourtant .test est un TLD réservé (RFC 2606) qui ne délivrera jamais rien. La conformité syntaxique et la délivrabilité sont des propriétés indépendantes, et une regex ne voit que la première.

ReDoS : quand la regex est la vulnérabilité

Une regex sujette au backtracking, exécutée côté serveur dans Node.js, peut être exploitée avec une entrée soigneusement construite pour bloquer la boucle d’événements — un vecteur de déni de service (CWE-1333 : Complexité inefficace des expressions régulières, et la référence OWASP ReDoS) qui n’a rien à voir avec les e-mails et tout à voir avec le backtracking catastrophique. Les motifs comportant des quantificateurs imbriqués ou adjacents sur des classes de caractères qui se chevauchent peuvent prendre un temps exponentiel sur des entrées qui correspondent presque.

Voici une démonstration reproductible. Le (...)+ du motif encapsule un groupe qui peut correspondre au même caractère de plusieurs façons, de sorte qu’une longue suite d’un même caractère suivie d’un caractère non correspondant force le moteur à essayer un nombre exponentiellement grand de partitions avant d’échouer :

// Node.js v24. Exécuter avec : node redos.js
// Un motif délibérément vulnérable, sujet au backtracking.
const evil = /^([a-zA-Z0-9]+)*@example\.com$/;

// Une quasi-correspondance construite : de nombreux 'a', puis un caractère qui brise la correspondance.
const attack = "a".repeat(40) + "!";

console.time("redos");
evil.test(attack); // bloque la boucle d'événements
console.timeEnd("redos");

Sur une version récente de Node.js, augmenter le nombre de répétitions fait croître le temps de correspondance de façon explosive — chaque caractère ajouté double approximativement le travail. Comme le moteur de regex de Node s’exécute de façon synchrone sur le thread principal, une seule requête contenant cette entrée paralyse la boucle d’événements et bloque toutes les autres requêtes en cours. La forme (x+)* est le signal d’alarme : tout groupe pouvant correspondre à la même sous-chaîne de plus d’une façon, sous un quantificateur externe, est un candidat au backtracking catastrophique. La solution n’est pas un motif plus intelligent — c’est de ne pas construire ce type de motif du tout, ce que vous obtenez précisément en déléguant à la plateforme ou à une bibliothèque maintenue.

La syntaxe n’est pas la délivrabilité

Même une adresse parfaitement conforme aux RFC ne vous dit rien sur l’arrivée effective des messages. Une regex ne peut pas vérifier que le domaine existe, qu’il possède des enregistrements MX, que la boîte aux lettres est provisionnée, ou que l’adresse n’est pas une adresse jetable. Ce sont des questions de réseau et de politique, pas des questions de grammaire. Une adresse comme realuser@gmail.com et une faute de frappe comme realuser@gmial.com sont toutes deux syntaxiquement valides ; seule une requête DNS les distingue, et seule une livraison effective distingue une boîte aux lettres active d’une boîte morte.

Les domaines d’e-mail jetables et temporaires constituent une préoccupation connexe mais distincte : des adresses syntaxiquement et opérationnellement valides, mais qui existent pour contourner votre inscription. Les détecter nécessite une liste de blocage maintenue des domaines de ces fournisseurs, pas un motif — la liste des domaines change constamment, et toute liste codée en dur devient rapidement obsolète. Traitez cela comme une couche de politique au-dessus de la validation, et non comme une partie de celle-ci.

Que faire à la place

Utilisez l’approche en couches : une vérification minimale de base pour l’UX, la validation intégrée de la plateforme pour la syntaxe, une bibliothèque maintenue uniquement si vous avez besoin de plus, et un e-mail de confirmation pour la seule chose qui compte vraiment. Voici l’ordre, du moins coûteux au plus fiable.

1. Une vérification de base minimale

Pour un retour immédiat côté client, le motif le plus petit et le plus utile est celui issu de l’argument originel « arrêtez de valider avec des regex » : exiger quelque chose, un @, quelque chose, un point, et quelque chose. Associez-le à une vérification de longueur.

/**
 * Vérification de base de la Couche 1 : détecte les fautes de frappe évidentes, rien de plus.
 * Délibérément permissive — ce n'est PAS une preuve de validité.
 * @param value - la chaîne brute saisie
 * @returns true si la valeur a la forme approximative d'un e-mail et fait <= 254 caractères
 */
export function looksLikeEmail(value: string): boolean {
  if (value.length > 254) return false; // RFC 5321 §4.5.3.1.3
  return /.+@.+\..+/.test(value);
}

Cette vérification de base rejette alicegmail.com et alice@localhost, accepte le plus-addressing et les gTLD longs, et s’exécute en temps constant. Il n’est pas sûr de traiter son résultat true comme « valide » — c’est un détecteur de fautes de frappe.

2. Préférez la plateforme : <input type="email">

Le meilleur choix par défaut pour la validation syntaxique est le propre <input type="email"> du navigateur, et il vaut la peine de savoir exactement ce qu’il fait. Le <input type="email"> du HTML Living Standard utilise une regex que la spécification elle-même qualifie de « violation délibérée de la RFC 5322 » — elle n’est intentionnellement pas conforme à la RFC, privilégiant l’utilisabilité à la précision de la spécification, et c’est un meilleur choix par défaut que tout motif que vous écrirez vous-même. La spécification cite le motif exact :

^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$

Clause par clause :

  • [a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+ — la partie locale, autorisant les caractères spéciaux atext. Elle ne supporte délibérément pas les parties locales entre guillemets ("user name"@…).
  • @ — exactement un séparateur.
  • [a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])? — un label de domaine : commence et se termine par un caractère alphanumérique, les tirets sont autorisés à l’intérieur, limité à 63 caractères. Elle ne supporte délibérément pas les domaines IP littéraux (user@[192.168.1.1]).
  • (?:\.<label>)* — zéro ou plusieurs labels supplémentaires séparés par des points, de sorte que les domaines à un seul label et à plusieurs labels passent tous les deux.

Le WHATWG documente ouvertement ce compromis : ce motif rejette intentionnellement certaines adresses RFC 5322 techniquement valides (parties entre guillemets, IP littéraux), car ces formes sont extrêmement rares dans les inscriptions réelles et les supporter introduit plus de bugs qu’elles n’en évitent. C’est le bon compromis pour un champ de formulaire, et c’est pourquoi <input type="email"> devrait être votre référence de Couche 2 — il ne présente aucune pathologie de backtracking et correspond à ce que les navigateurs appliquent déjà.

3. Recourez à une bibliothèque maintenue uniquement si vous avez besoin de plus

Si vous avez besoin d’une validation syntaxique côté serveur au-delà du motif HTML5, utilisez une bibliothèque maintenue et bien testée plutôt que de créer la vôtre. Le package validator (npm validator, licence MIT) expose une fonction isEmail qui supporte les parties locales entre guillemets et fournit des options pour les domaines IP littéraux et les noms d’affichage :

import isEmail from "validator/lib/isEmail";

/**
 * Validation syntaxique de la Couche 2, côté serveur.
 * @param email - adresse candidate (dont la longueur a déjà été vérifiée)
 * @returns true si syntaxiquement valide selon les règles alignées sur les RFC de validator
 */
export function isSyntacticallyValid(email: string): boolean {
  return isEmail(email, { allow_utf8_local_part: true });
}

Préférez ceci à l’ancien package email-validator, qui n’a pas été publié depuis 2018. Une bibliothèque vous offre une gestion testée des cas limites et un mainteneur actif qui corrige les cas que votre motif écrit à la main ne gérera jamais — y compris, avec les bonnes options, les adresses EAI.

4. La vraie réponse : envoyez un e-mail de confirmation

La seule étape qui prouve qu’une adresse fonctionne est la livraison. Envoyez un message de confirmation avec un lien à usage unique ; ne considérez l’adresse comme vérifiée qu’après que l’utilisateur a cliqué dessus. C’est le double opt-in, et il rend la validation en amont élaborée superflue — une adresse malformée ou non délivrable ne sera simplement jamais confirmée.

/**
 * Esquisse du flux de vérification. Le stockage et le mailer sont spécifiques à l'application.
 * @param email - une chaîne qui a déjà passé les vérifications de longueur et de syntaxe
 */
async function startEmailVerification(email: string): Promise<void> {
  const token = crypto.randomUUID();
  await storePendingVerification(email, token); // expire après, par exemple, 24h
  const link = `https://app.example.com/verify?token=${token}`;
  await sendMail(email, "Confirmez votre e-mail", `Cliquez pour confirmer : ${link}`);
  // Marquer le compte comme vérifié uniquement lorsque /verify est appelé avec un token valide.
}

L’envoi d’un e-mail de confirmation est la solution à laquelle source après source finit par aboutir, pour la même raison : elle accomplit en une seule étape ce qu’aucune regex ne peut faire. Comme l’a dit Jamie Zawinski : « Certaines personnes, confrontées à un problème, se disent : “Je sais, je vais utiliser des expressions régulières.” Elles ont maintenant deux problèmes. » Pour les e-mails, le second problème est que la regex n’a toujours pas répondu à la question.

Conclusion

Cessez d’essayer de valider l’adresse et commencez à essayer de vérifier la boîte aux lettres. Utilisez un motif minimal associé à une limite de 254 caractères pour un retour UX immédiat, appuyez-vous sur <input type="email"> ou une bibliothèque maintenue comme validator pour la syntaxe, et conditionnez tout vrai compte à un e-mail de confirmation — cette dernière étape est la seule qui prouve que quelqu’un est bien là. La prochaine fois qu’un formulaire d’inscription a besoin d’un champ e-mail, optez pour la plateforme et le flux de confirmation, pas pour le motif trouvé sur Stack Overflow.

FAQ

Quelle est la longueur maximale valide d'une adresse e-mail ?

Une adresse e-mail est limitée à 254 caractères. Cela découle de la section 4.5.3.1.3 de la RFC 5321, qui limite le forward-path à 256 octets, crochets angulaires inclus, laissant 254 caractères pour l'adresse elle-même. La partie locale est séparément limitée à 64 octets et le domaine à 255 octets. Une simple comparaison de longueur applique cela correctement, ce qui est la seule validation qui vaille la peine d'être effectuée avant tout motif.

Le champ e-mail HTML5 valide-t-il selon la grammaire complète de la RFC 5322 ?

Non. Le HTML Living Standard décrit explicitement la regex de son champ e-mail comme une « violation délibérée de la RFC 5322 ». Il rejette intentionnellement des formes techniquement valides comme les parties locales entre guillemets ('user name'@example.com) et les domaines IP littéraux (user@[192.168.1.1]), car celles-ci sont extrêmement rares dans les inscriptions réelles. Ce compromis favorise l'utilisabilité par rapport à la conformité à la spécification, ce qui en fait un choix par défaut plus sûr qu'un motif écrit à la main, mais ce n'est pas un validateur RFC complet.

Comment une regex de validation d'e-mail peut-elle provoquer une attaque par déni de service ?

Une regex avec des quantificateurs imbriqués ou adjacents sur des classes de caractères qui se chevauchent, comme la forme ([a-zA-Z0-9]+)*, peut prendre un temps exponentiel sur des entrées qui correspondent presque. C'est le backtracking catastrophique, classifié comme CWE-1333. Exécutée côté serveur dans Node.js, où le moteur de regex s'exécute de façon synchrone sur le thread principal, une seule requête soigneusement construite peut paralyser la boucle d'événements et bloquer toutes les autres requêtes en cours. La solution est d'éviter entièrement cette classe de motifs, pas d'en écrire un plus intelligent.

Une regex peut-elle vérifier si une adresse e-mail existe réellement ?

Non. Une regex n'inspecte que la forme de la chaîne ; elle ne peut pas vérifier que le domaine existe, qu'il possède des enregistrements MX, ou que la boîte aux lettres est provisionnée. La conformité syntaxique et la délivrabilité sont des propriétés indépendantes. Une adresse comme realuser@gmial.com est syntaxiquement valide mais non délivrable à cause d'une faute de frappe, et someone@validformat.test passe la plupart des motifs mais utilise un TLD réservé qui ne délivre jamais rien. Seul un e-mail de confirmation délivré avec succès prouve qu'une adresse reçoit des messages.

Pourquoi les regex d'e-mail rejettent-elles des adresses valides comme name+filter@gmail.com ?

Le plus-addressing est entièrement valide car le signe plus est un atext ordinaire selon la section 3.2.3 de la RFC 5322 et la section 4.1.2 de la RFC 5321. Les motifs qui le rejettent, ainsi que les adresses sur des gTLD longs comme .photography ou les adresses internationalisées définies dans la RFC 6531, ne sont pas stricts — ils sont simplement dans l'erreur. Ces faux négatifs atteignent la production parce que les suites de tests utilisent test@example.com, qui passe tous les motifs, de sorte que le rejet de vraies adresses ne remonte jamais dans les tests.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.