E-mails OTP et de vérification : rapidité, sécurité et bonnes pratiques

Générer, stocker et envoyer par e-mail des codes à usage unique et liens de vérification en toute sécurité : livraison rapide, expiration, limites et modèle fiable.

L’e-mail contenant un code à usage unique est le message le plus attendu que votre produit enverra jamais. L’utilisateur fixe un écran qui affiche « Saisissez le code à 6 chiffres que nous vous avons envoyé », et chaque seconde que met l’e-mail à arriver est une seconde pendant laquelle il se demande s’il ne va pas abandonner.

Les e-mails de vérification se situent aussi exactement sur votre périmètre de sécurité. Mal conçus, ils bloquent de vrais utilisateurs ou laissent entrer des attaquants. Ce guide couvre les deux aspects : faire arriver le code rapidement en boîte de réception, et garantir que seule la bonne personne peut l’utiliser.

Codes ou liens ?

Il existe deux formats courants :

  • Codes à usage unique (OTP) : un nombre court comme 482913, à saisir dans votre application. Idéal pour la connexion et la validation en deux étapes, et cela fonctionne même lorsque l’utilisateur lit ses e-mails sur un autre appareil.
  • Liens de vérification : un long jeton aléatoire dans une URL. Idéal pour confirmer une adresse e-mail à l’inscription ou pour réinitialiser un mot de passe, car un seul clic termine le parcours.

Tout ce qui suit s’applique aux deux. La différence tient surtout à la longueur : un lien peut contenir un long jeton, un code doit être assez court pour être saisi.

Générer les codes de façon sécurisée

Utilisez le générateur aléatoire cryptographiquement sûr de votre langage. Math.random() et le module random de Python sont prévisibles et ne doivent jamais servir à générer des codes de sécurité.

// Node.js
import { randomInt, createHmac } from 'node:crypto';

const code = String(randomInt(0, 1_000_000)).padStart(6, '0'); // "048213"
// Keyed hash: OTP_SECRET lives in your secrets manager, never in the database.
const codeHash = createHmac('sha256', process.env.OTP_SECRET).update(`${user.id}:${code}`).digest('hex');
# Python
import hashlib
import hmac
import os
import secrets

code = f"{secrets.randbelow(1_000_000):06d}"
# Keyed hash: OTP_SECRET lives in your secrets manager, never in the database.
code_hash = hmac.new(os.environ["OTP_SECRET"].encode(), f"{user_id}:{code}".encode(), hashlib.sha256).hexdigest()

Ne stockez qu’un hachage avec clé (HMAC), accompagné de l’identifiant de l’utilisateur, d’une date d’expiration et d’un compteur de tentatives. Un simple SHA-256 ne suffit pas : avec seulement un million de codes possibles, quiconque détient la base de données pourrait tous les essayer en moins d’une seconde. Le HMAC nécessite un secret conservé hors de la base de données, de sorte qu’une fuite de la base seule ne révèle pas les codes actifs.

Expiration rapide, tentatives limitées

Un code à six chiffres offre un million de possibilités. C’est largement suffisant, à condition que les attaquants ne puissent pas deviner indéfiniment :

Contrôle Valeur recommandée
Durée de vie du code 10 minutes pour les codes de connexion ; jusqu’à 24 heures pour les liens de confirmation d’adresse e-mail
Tentatives erronées par code 5, après quoi le code est invalidé
Codes envoyés par adresse Environ 3 par tranche de 15 minutes, avec un délai d’attente visible sur « Renvoyer »
Codes envoyés par adresse IP Un plafond plus élevé, pour empêcher un attaquant de passer en revue de nombreuses adresses
Utilisation Usage unique : supprimez le code ou marquez-le comme utilisé dès qu’il a été validé

Comparez toujours les codes en temps constant (crypto.timingSafeEqual en Node.js, hmac.compare_digest en Python), et l’émission d’un nouveau code doit invalider le précédent.

L’envoyer immédiatement

Les e-mails OTP doivent partir dans la requête même qui les a demandés, et non dans une tâche groupée exécutée toutes les minutes. Avec Inbixio, cela tient en un appel :

await fetch('https://backend.inbixio.com/api/send-email/', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.INBIXIO_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    to_email: user.email,
    subject: `${code} is your YourApp sign-in code`,
    message: otpTemplate({ code, minutes: 10 }),
  }),
  signal: AbortSignal.timeout(10_000),
});

Inbixio accepte la requête et met la livraison en file d’attente immédiatement : votre point de terminaison peut donc répondre rapidement. Si l’appel échoue, prévenez l’utilisateur tout de suite (« Nous n’avons pas pu envoyer votre code. Veuillez réessayer dans un instant ») au lieu de le laisser attendre un e-mail qui n’arrivera jamais. Gérer les erreurs d’une API e-mail indique quelles erreurs méritent une nouvelle tentative automatique.

Deux conseils de livraison propres aux codes :

  • Utilisez un expéditeur dédié, comme security@yourco.com, connecté comme serveur à part entière, avec une clé API qui lui est rattachée (rattachez à nouveau la clé si vous supprimez un jour ce serveur). Les utilisateurs apprennent à faire confiance à cette adresse, et les campagnes marketing ne peuvent pas affecter sa réputation.
  • Authentifiez votre domaine. Un code qui atterrit dans les spams est un code qui n’arrive jamais. Suivez la checklist SPF, DKIM et DMARC.

Rédiger un e-mail impossible à confondre avec du phishing

Votre modèle est en concurrence avec des e-mails de phishing qui copient exactement ce format. Rendez le vrai facile à reconnaître :

  • Mettez le code dans l’objet, par ex. « 482913 is your YourApp sign-in code ». Les utilisateurs peuvent le lire depuis la notification sans ouvrir l’e-mail, et les claviers de téléphone peuvent le suggérer.
  • Indiquez à quoi il sert et combien de temps il est valable : « Utilisez ce code pour vous connecter à YourApp. Il expire dans 10 minutes. »
  • Dites quoi faire si ce n’était pas eux : « Si vous n’avez pas essayé de vous connecter, vous pouvez ignorer cet e-mail. Votre compte est en sécurité. »
  • Ne redemandez jamais le code. Dites-le clairement : « YourApp ne vous demandera jamais ce code par téléphone ou par e-mail. »
  • Restez bref, sans marketing, et réduisez les liens au strict minimum.

Un modèle HTML simple et accessible :

<div style="font-family:system-ui,sans-serif;max-width:480px;margin:auto;color:#111">
  <p>Use this code to sign in to YourApp:</p>
  <p style="font-size:32px;font-weight:700;letter-spacing:6px;margin:24px 0">482913</p>
  <p>It expires in 10 minutes. If you didn't try to sign in, you can ignore this email.</p>
  <p style="color:#666;font-size:13px">YourApp will never ask you for this code.</p>
</div>

Les styles en ligne sont volontaires. De nombreux clients de messagerie suppriment les blocs <style> : le CSS en ligne est donc le moyen le plus fiable de mettre en forme un e-mail.

Ne révélez pas quelles adresses ont un compte

Sur un écran de connexion ou « envoyez-moi un code », affichez la même réponse que l’adresse soit enregistrée ou non : « Si un compte existe pour cette adresse, nous avons envoyé un code. » Sinon, votre formulaire devient un outil gratuit pour vérifier quelles adresses e-mail utilisent votre produit.

Checklist

  • Codes issus d’un générateur cryptographiquement sûr ; seuls des HMAC stockés, avec le secret hors de la base de données
  • Expiration à 10 minutes, usage unique, 5 tentatives, comparaison en temps constant
  • Limites de débit par adresse et par IP, avec un délai avant renvoi
  • Envoi immédiat, avec un message d’erreur clair en cas d’échec
  • Adresse d’expédition dédiée et authentifiée
  • Code dans l’objet, finalité et expiration dans le corps, consigne « ce n’était pas vous ? »
  • Même réponse que le compte existe ou non

Envoyez-le avec Inbixio

Connectez votre propre serveur SMTP, créez une clé API aux droits limités et envoyez depuis n’importe quel langage en une seule requête HTTPS.

Créer un compte Voir les tarifs