Un code OTP (one-time password) envoyé par SMS confirme qu’un utilisateur possède bien le numéro qu’il a saisi. C’est le moyen le plus courant de vérifier une inscription, une connexion ou un paiement. Ce guide montre comment le faire correctement en Node.js, de la génération du code à sa vérification.
Le principe
- L’utilisateur saisit son numéro.
- Votre serveur génère un code à 6 chiffres, en garde une empreinte (jamais le code en clair) avec une date d’expiration.
- Le code part par SMS.
- L’utilisateur saisit le code ; votre serveur compare les empreintes et vérifie l’expiration et le nombre d’essais.
1. Générer et stocker le code
Utilisez crypto.randomInt, jamais Math.random, qui n’est pas fait pour la sécurité.
import { randomInt, createHash } from "node:crypto";
const hash = (code) => createHash("sha256").update(code).digest("hex");
export function createOtp(phone) {
const code = String(randomInt(0, 1_000_000)).padStart(6, "0");
const record = {
phone,
codeHash: hash(code),
expiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes
attempts: 0,
};
return { code, record }; // enregistrez record dans votre base ou dans Redis
}
2. Envoyer le code par SMS
Node.js 18 et plus inclut fetch : aucune dépendance n’est nécessaire.
export async function sendOtp(phone, code, requestId) {
const response = await fetch("https://api.sendimo.tn/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SENDIMO_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `otp-${requestId}`,
},
body: JSON.stringify({
to: phone, // format E.164 : +21620123456
from: "SENDIMO",
text: `Votre code est ${code}. Il expire dans 5 minutes.`,
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message}`);
}
return response.json(); // { id, status: "queued", segments: 1, cost, ... }
}
L’Idempotency-Key évite d’envoyer deux codes si votre serveur réessaie la même requête après une coupure réseau.
3. Vérifier le code
const MAX_ATTEMPTS = 5;
export function verifyOtp(record, input) {
if (Date.now() > record.expiresAt) return { ok: false, reason: "expired" };
if (record.attempts >= MAX_ATTEMPTS) return { ok: false, reason: "locked" };
record.attempts += 1; // à sauvegarder
if (hash(input.trim()) !== record.codeHash) return { ok: false, reason: "wrong_code" };
return { ok: true }; // supprimez le record : un code ne sert qu’une fois
}
4. Se protéger contre les abus
Un formulaire OTP ouvert peut servir à envoyer des SMS à vos frais. Quelques règles simples :
- Limitez les envois : par exemple 1 code par minute et 5 par heure pour un même numéro, et une limite par adresse IP.
- Limitez les essais : 5 tentatives par code, puis un nouveau code obligatoire.
- Expirez vite : 5 minutes suffisent dans la plupart des cas.
- Ne dites pas si le numéro existe dans votre base : renvoyez le même message dans tous les cas.
Rédiger un bon SMS OTP
- Restez en GSM-7 et sous 160 caractères pour ne payer qu’un segment : pas d’emoji, et évitez ê, â, ô et ç, qui forcent l’Unicode.
- Mettez le code au début du message : il apparaît dans la notification sans ouvrir le SMS.
- Indiquez la durée de validité et, si besoin, « ne communiquez jamais ce code ».
Tester avant la mise en production
Avec une clé sk_test_, +21600000001 finit toujours en delivered et +21600000002 toujours en failed. Rien n’est envoyé ni facturé : vous pouvez tester l’échec de livraison comme le succès.
Combien coûte un OTP ?
Un code d’un segment coûte de 0,100 DT à 0,150 DT selon votre offre, soit 120 DT pour 1 000 vérifications avec le pack Pro. Le détail est dans notre article sur le prix d’un SMS en Tunisie.
Pour aller plus loin
Abonnez-vous pour recevoir chaque jour nos analyses et les actualités de l’entreprise.