Saltar al contenido

Autenticación segura en aplicaciones web: lo que no puedes ignorar

CS
Chakana Studio Software
· · 10 min de lectura

La autenticación es el componente de seguridad más crítico de cualquier aplicación web. Es también el que más frecuentemente encontramos mal implementado en proyectos que llegan a nosotros para revisión o mantenimiento.

No porque los desarrolladores no sepan que importa. Sino porque los errores son sutiles y sus consecuencias no se ven hasta que alguien los explota.

Los errores más comunes que vemos en producción

1. Contraseñas almacenadas con MD5 o SHA-1

Todavía en 2025 encontramos bases de datos con contraseñas hasheadas con MD5 o SHA-256 sin sal. Estos hashes son susceptibles a ataques de diccionario y rainbow tables — con hardware moderno, una contraseña débil se puede crackear en segundos.

La solución: usa bcrypt, Argon2 o scrypt. Son algoritmos diseñados específicamente para contraseñas, con un factor de costo que puedes ajustar para que el hash sea más lento de calcular a medida que el hardware mejora.

import bcrypt from 'bcrypt';

// Almacenar
const hash = await bcrypt.hash(password, 12); // 12 rondas

// Verificar
const valid = await bcrypt.compare(inputPassword, storedHash);

2. Tokens JWT sin expiración (o con expiración muy larga)

Un JWT sin fecha de expiración es válido para siempre. Si ese token se filtra — en un log, en un error de cliente, en una extensión de browser maliciosa — el atacante tiene acceso indefinido.

La solución: expira los access tokens en minutos (15–60 min) y usa refresh tokens de larga duración almacenados en cookies HttpOnly para renovarlos. Si necesitas invalidar sesiones, mantén una lista de refresh tokens en Redis o la base de datos.

3. Cookies sin las flags correctas

Una cookie de sesión sin HttpOnly puede ser robada por JavaScript malicioso (XSS). Sin Secure se envía en HTTP sin cifrar. Sin SameSite=Strict o Lax es vulnerable a CSRF.

La solución:

Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/

4. Rate limiting ausente en el endpoint de login

Sin rate limiting, un atacante puede intentar millones de combinaciones de contraseña por fuerza bruta. El endpoint de login es el más atacado de cualquier aplicación.

La solución: limita los intentos por IP y por usuario. Después de 5 intentos fallidos en 15 minutos, introduce un delay progresivo o bloquea temporalmente. Usa Upstash Redis o similar para el conteo distribuido.

5. Restablecimiento de contraseña con tokens predecibles

Un token de reset que es solo el timestamp del servidor, o el ID del usuario encodeado en base64, puede ser adivinado o predecido.

La solución: genera tokens de reset con crypto.randomBytes(32) o equivalente. Guarda el hash del token en la base de datos (no el token en claro), y dale una expiración corta (15–30 minutos).

6. No invalidar sesiones al cambiar contraseña

Si un usuario cambia su contraseña porque sospecha que alguien entró a su cuenta, espera que esa acción cierre todas las sesiones activas. Si no lo hace, el atacante que ya está adentro sigue estando adentro.

La solución: al cambiar contraseña, invalida todos los refresh tokens del usuario y revoca las sesiones activas.

Checklist de autenticación segura

Antes de lanzar cualquier sistema con autenticación:

  • Contraseñas hasheadas con bcrypt/Argon2 (factor de costo ≥ 10)
  • Tokens JWT con expiración corta (≤ 60 min para access tokens)
  • Refresh tokens en cookies HttpOnly
  • Cookies con flags Secure, HttpOnly, SameSite
  • Rate limiting en login y registro
  • Tokens de reset de contraseña con crypto.randomBytes
  • Invalidación de sesiones al cambiar contraseña
  • Logs de intentos fallidos de autenticación
  • HTTPS forzado (sin HTTP permitido)
  • Headers de seguridad (HSTS, X-Frame-Options, CSP)

Autenticación de dos factores (2FA): cuándo y cómo

Para cualquier aplicación que maneje dinero, datos personales sensibles o accesos privilegiados, la contraseña por sí sola ya no alcanza. El 2FA agrega una segunda capa que un atacante no tiene aunque haya robado la contraseña.

Las opciones, de menos a más seguras:

  • SMS (OTP por mensaje de texto): mejor que nada, pero vulnerable a ataques de SIM swapping. Úsalo solo si el usuario no tiene alternativa.
  • App TOTP (Google Authenticator, Authy): el estándar práctico. Genera códigos de 6 dígitos cada 30 segundos sin depender de la red móvil. Se implementa con librerías maduras (otplib en Node, pyotp en Python).
  • Passkeys / WebAuthn: el futuro. Usa la biometría del dispositivo (huella, Face ID) o una llave física. Resistente a phishing porque la credencial está ligada al dominio. Cada vez más soportado por navegadores y vale la pena adoptarlo en productos nuevos.

La regla práctica: ofrece TOTP como mínimo para cuentas con datos sensibles, y empieza a evaluar passkeys para productos nuevos. Guarda siempre códigos de respaldo de un solo uso para cuando el usuario pierda el dispositivo.

El error silencioso: enumerar usuarios sin querer

Un detalle que casi nadie revisa: tu sistema de login no debe revelar si un email existe o no. Si al ingresar un email no registrado respondes “este usuario no existe” y con uno registrado pero contraseña mala respondes “contraseña incorrecta”, le estás dando a un atacante una lista de qué cuentas existen.

La defensa es simple: usa el mismo mensaje genérico en ambos casos (“Email o contraseña incorrectos”) y el mismo tiempo de respuesta. Lo mismo aplica al “olvidé mi contraseña”: responde siempre “si el email existe, enviamos instrucciones”, exista o no la cuenta.

Para aplicaciones B2B o dashboards internos, los magic links eliminan la gestión de contraseñas por completo. El usuario ingresa su email, recibe un enlace de un solo uso con expiración de 15 minutos, y accede directamente.

Es más seguro que la mayoría de implementaciones de contraseña que vemos, más fácil de mantener, y elimina el problema de contraseñas débiles o reutilizadas.

Usamos magic links en el panel de administración de este mismo sitio.


Si tienes una aplicación en producción y no estás seguro de cómo está implementada la autenticación, una revisión de seguridad puede identificar vulnerabilidades antes de que alguien más lo haga.

Preguntas frecuentes

¿Con qué algoritmo debo hashear las contraseñas?

Con bcrypt, Argon2 o scrypt — algoritmos diseñados específicamente para contraseñas, con un factor de costo ajustable. Nunca uses MD5 ni SHA-1/SHA-256 sin sal: son rápidos de calcular y vulnerables a ataques de diccionario y rainbow tables. Para bcrypt, un factor de costo de 12 es un buen punto de partida en 2026.

¿Cuánto debe durar un token JWT?

Los access tokens deben expirar rápido: entre 15 y 60 minutos. Para mantener la sesión sin pedir login constante, usa refresh tokens de larga duración guardados en cookies HttpOnly, y mantén una lista de refresh tokens válidos en Redis o la base de datos para poder revocarlos. Un JWT sin expiración es válido para siempre si se filtra.

¿Qué flags debe tener una cookie de sesión?

Tres como mínimo: HttpOnly (evita que JavaScript malicioso la robe vía XSS), Secure (solo se envía por HTTPS) y SameSite=Lax o Strict (protege contra CSRF). Una cookie de sesión sin estas flags es uno de los errores de seguridad más comunes que encontramos en producción.

¿Por qué necesito rate limiting en el login?

Sin rate limiting, un atacante puede probar millones de combinaciones de contraseña por fuerza bruta. El endpoint de login es el más atacado de cualquier aplicación. La defensa: limitar intentos por IP y por usuario, e introducir un delay progresivo o bloqueo temporal después de varios fallos (por ejemplo, 5 intentos en 15 minutos).

¿Son los magic links más seguros que las contraseñas?

Para muchos casos B2B y paneles internos, sí. Los magic links eliminan la gestión de contraseñas: el usuario recibe un enlace de un solo uso con expiración corta. Esto evita contraseñas débiles, reutilizadas o mal hasheadas. No son ideales para todos los productos de consumo masivo, pero para dashboards y herramientas internas suelen ser más seguros y fáciles de mantener.

Compartir artículo

Hablemos

De tu idea
al
producto.

Una conversación de 15 minutos aclara más que un brief de 3 páginas. Te decimos si podemos ayudarte, cuánto costaría y cuándo estaría listo.

Iniciar proyecto WhatsApp directo

Respuesta en menos de 24h

Precio y alcance fijos por escrito Demos en vivo cada 2 semanas El código y los datos son tuyos