Rate limiting y expiración
Defiende tu flujo de OTP contra fuerza bruta y contra el fraude de tráfico.
Un OTP de 6 dígitos tiene un millón de combinaciones. Sin límite de intentos, un atacante lo adivina en minutos. Sin límite de emisión, alguien puede usar tu formulario para enviar miles de SMS a tu costo.
Límites recomendados
- Expiración: 5 a 10 minutos.
- Intentos de verificación: 5 por código; luego invalidar.
- Emisiones: 3 por número cada 15 minutos; reenvío con espera creciente (30 s, 60 s, 120 s).
- Por IP y por prefijo: límites globales para detectar ataques distribuidos.
async function verify(phone, input) {
const rec = await store.get(phone);
if (!rec || Date.now() > rec.exp) return { ok: false, reason: "expired" };
if (rec.attempts >= 5) return { ok: false, reason: "locked" };
rec.attempts++;
const ok = safeEqual(rec.hash, await sha256(phone + ":" + input));
if (ok) await store.delete(phone); else await store.set(phone, rec);
return { ok };
}Un código de seis dígitos tiene un millón de combinaciones. Sin límite de intentos, un atacante automatizado lo adivina en minutos. Con cinco intentos por código y expiración de cinco minutos, la probabilidad de acertar cae a cinco en un millón por emisión.
Limita en dos ejes: intentos de verificación por código y emisiones por número y por IP. El segundo eje te protege del SMS pumping, un fraude en el que bots disparan miles de OTP hacia números premium para cobrar comisiones.
Pruébalo
// Lock the code after 3 failed attempts
const phone = "+56912345678";
const intentos = {};
function verificar(code) {
intentos[phone] = (intentos[phone] || 0) + 1;
if (intentos[phone] > 3) {
console.error("Bloqueado: demasiados intentos. Solicita un código nuevo.");
return false;
}
return mx.otp.verify(phone, code);
}
const real = mx.otp.issue(phone);
verificar("000000");
verificar("123456");
verificar("999999");
const ok = verificar(real); // fourth attempt: locked even with the right code
console.log("acceso concedido:", ok);