Diseña un sistema de autenticación seguro desde cero — recorre el registro, el login, la emisión de sesión/token y el reseteo de contraseña
La pregunta de entrevista senior de mayor rendimiento aquí — una buena respuesta recorre cada etapa en orden, nombrando la decisión exacta y el modo de falla en cada una.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
Diseña un sistema de autenticación seguro desde cero — recorre el registro, el login, la emisión de sesión/token y el reseteo de contraseña
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Por qué esta pregunta es en realidad una pregunta de diseño de sistemas con sombrero de seguridad
"Diseña un sistema de autenticación" es una de las preguntas de entrevista senior de mayor rendimiento precisamente porque obliga a tomar una docena de decisiones pequeñas, cada una con una respuesta incorrecta bien conocida que sigue apareciendo en brechas reales: inventar tu propio hashing de contraseñas, devolver errores distintos para "contraseña incorrecta" versus "cuenta inexistente", guardar un token de reseteo de contraseña en texto plano, nunca rotar los refresh tokens, poner un token de vida larga en un lugar que JavaScript pueda leer. Una buena respuesta recorre el registro, el login, la emisión de tokens y el reseteo de contraseña en orden, nombrando la decisión específica en cada paso y por qué la alternativa es un modo de falla conocido — no recitando una lista de palabras de moda.
#Registro
El registro tiene que lograr tres cosas a la vez: crear la cuenta, probar que la persona controla el correo que dice tener, y nunca filtrar si un correo ya existe en el sistema a alguien que no está autorizado para saberlo.
- Validar la entrada del lado del servidor. Los medidores de fortaleza de contraseña del lado del cliente son un detalle de UX, no un control — el servidor debe hacer cumplir la política real de forma independiente, porque nada impide que una solicitud se salte el navegador por completo.
- Seguir las guías modernas de política de contraseñas, no reglas de composición. La guía de NIST 800-63B (que hoy siguen la mayoría de los sistemas serios) favorece una longitud mínima razonable por sobre la complejidad forzada — la longitud importa mucho más para resistir el cracking que exigir un dígito y un símbolo, y las reglas de complejidad en la práctica solo empujan a los usuarios hacia sustituciones predecibles. Verificar la contraseña contra una lista de contraseñas filtradas (mediante una API de rango con k-anonimato, para que la contraseña en texto plano nunca salga del cliente) detiene más ataques reales de los que jamás detendrá una regla de composición.
- Hashear la contraseña con un KDF lento, intensivo en memoria y con sal — Argon2id es la recomendación por defecto actual, con bcrypt como alternativa bien entendida. Nunca guardes una contraseña de forma reversible, y nunca uses solo un hash rápido de propósito general como SHA-256 o MD5: esos están diseñados para velocidad, que es exactamente la propiedad equivocada para algo que un atacante intentará crackear offline a miles de millones de intentos por segundo con hardware especializado.
- Crear la cuenta en estado no verificado y enviar por correo un enlace de verificación construido alrededor de un token aleatorio de alta entropía — guardado con hash, igual que un token de reseteo de contraseña (más sobre esto abajo).
- Responder de forma idéntica sin importar si el correo ya estaba registrado — algo como "Si este correo puede registrarse, te enviamos una confirmación" — para que el endpoint de registro no pueda usarse para enumerar cuentas existentes. Limitar la tasa de intentos de registro por IP y por correo para frenar el abuso automatizado.
#Login
- El cliente envía las credenciales sobre TLS; el servidor busca la cuenta por correo.
- Si la cuenta no existe, el servidor debe igualmente ejecutar una comparación de hash de contraseña ficticia contra un hash fijo y falso antes de responder, para que el tiempo de respuesta no filtre "esta cuenta no existe" mediante un canal lateral de temporización — el mismo principio anti-enumeración del registro, aplicado al tiempo de respuesta en vez de al contenido.
- Verificar la contraseña enviada contra el hash almacenado usando la propia función de verificación de tiempo constante del KDF (las funciones de verificación de Argon2id y bcrypt ya hacen esto correctamente — nunca escribas una comparación manual
==de los bytes del hash). - Limitar la tasa y aplicar retrasos crecientes por IP y por cuenta, para frenar el credential stuffing y la fuerza bruta. El bloqueo de cuenta requiere cuidado: un bloqueo fijo tras N fallos es en sí mismo un vector de denegación de servicio que un atacante puede dirigir deliberadamente contra la cuenta de una víctima, así que un retraso creciente (o un desafío tipo CAPTCHA tras cierto umbral) suele ser preferible a un bloqueo directo en sistemas orientados al consumidor.
- Si la contraseña es correcta, la cuenta está verificada y cualquier paso de múltiple factor habilitado se supera, el servidor emite una sesión.
#Emisión de sesión y tokens
Existen dos enfoques dominantes, y una buena respuesta senior debería poder compararlos en vez de asumir uno por defecto:
| Sesión del lado del servidor (cookie opaca) | Token sin estado (JWT/PASETO) | |
|---|---|---|
| Qué guarda el cliente | Un ID de sesión aleatorio y sin significado | Un token firmado con claims (sub, exp, iat, scope) |
| Cómo se valida | El servidor lo busca en un almacén compartido (Redis/BD) en cada solicitud | El servidor verifica la firma; no necesita búsqueda para el access token en sí |
| Revocación | Trivial — borrar el registro del lado del servidor | Difícil para el access token en sí; se resuelve manteniendo TTLs cortos (5–15 min) y revocando el refresh token asociado |
| Consideración de escalado | Necesita un almacén de sesión compartido y de baja latencia accesible desde cada instancia | Naturalmente sin estado para el access token; el refresh token igual necesita estado del lado del servidor para ser revocable |
La mayoría de los sistemas en producción usan un híbrido: un access token firmado y de vida corta para autorizar llamadas a la API, junto con un refresh token de vida más larga que se rastrea del lado del servidor (o se emite como un valor opaco rotativo) específicamente para que pueda revocarse. La rotación de refresh tokens — emitir uno completamente nuevo en cada uso e invalidar de inmediato el que se acaba de usar — convierte el reuso de un refresh token robado en un evento detectable: si el token viejo, ya rotado, vuelve a presentarse alguna vez, eso es evidencia fuerte de que fue robado, y toda la familia de tokens puede revocarse en el acto.
Dónde se guarda el token también importa. Una cookie HttpOnly, Secure, SameSite=Lax (o Strict) no puede ser leída por JavaScript en absoluto, lo que cierra el robo de tokens vía XSS — pero a cambio necesita protección CSRF, ya que el navegador la adjuntará automáticamente. Guardar un token en localStorage evita el CSRF pero se lo entrega directamente a cualquier payload de XSS que tenga éxito; si un sistema tiene algún riesgo de inyección de scripts (y la mayoría lo tiene), esa disyuntiva generalmente favorece a la cookie.
#Reseteo de contraseña — el paso que más sistemas hacen mal
El reseteo de contraseña es el flujo de mayor riesgo de todo el sistema, porque está diseñado para permitir que una parte no autenticada recupere el acceso a una cuenta. Cada decisión aquí existe para asegurar que esa parte sea realmente el dueño de la cuenta y nadie más.
- El endpoint de solicitud responde de forma idéntica exista o no el correo — el mismo principio anti-enumeración del registro aplica acá con apuestas aún mayores, ya que un flujo de reseteo que confirma la existencia de una cuenta le regala al atacante una forma gratuita de construir una lista de objetivos.
- El token en sí debe ser un valor largo y criptográficamente aleatorio generado por un CSPRNG — nunca derivado de material predecible como un hash del ID de usuario y la marca de tiempo, que un atacante podría potencialmente reproducir.
- Solo se guarda un hash del token del lado del servidor, exactamente igual que una contraseña. Si el almacén de tokens llegara a filtrarse, los hashes filtrados no pueden reutilizarse como sí podrían hacerlo los tokens en crudo — esto es el mismo razonamiento por el que las contraseñas se hashean en vez de guardarse en texto plano.
- El token tiene vida corta — comúnmente entre 15 y 60 minutos — y es estrictamente de un solo uso: en el instante en que se canjea, se borra o se marca como usado, y cualquier otro token de reseteo pendiente para esa cuenta también debería invalidarse.
- Canjear el token también es un buen momento para terminar las sesiones existentes. Si un atacante ya tiene una sesión activa en la cuenta, forzar un reseteo de contraseña debería terminar su acceso, no simplemente añadir una contraseña nueva junto a la suya.
- Se envía un correo de "tu contraseña fue cambiada" sin importar el caso, como señal fuera de banda para el verdadero dueño por si el reseteo no fue suyo — muchas veces es la primera y única advertencia que recibe una víctima de que algo pasó.
#Uniendo todo
La verdadera ventaja de un candidato senior en esta pregunta no está en saber que Argon2id existe — está en notar que el mismo puñado de principios se repite en cada etapa: nunca dejar que una respuesta filtre si una cuenta existe, nunca guardar un secreto (contraseña o token) en una forma que pueda reutilizarse si se filtra, mantener pequeño el radio de impacto de cada credencial y corta su vida útil, y tratar el momento de recuperación (reseteo de contraseña) como algo al menos tan sensible como el momento de autenticación inicial, porque es el paso que un atacante más probablemente atacará una vez que consiguió una sola pieza del rompecabezas — un correo filtrado, un código de verificación robado por phishing, un ticket de soporte olvidado.
🧠 Check your understanding
0/3 · 0/3 answered1. ¿Por qué un endpoint de login debería ejecutar una comparación de hash de contraseña ficticia incluso cuando el correo enviado no coincide con ninguna cuenta?
2. ¿Por qué es valiosa la rotación de refresh tokens (emitir uno nuevo en cada uso e invalidar el anterior) más allá de simplemente limitar la vida del token?
3. ¿Por qué un token de reseteo de contraseña debería guardarse del lado del servidor solo como hash, nunca en texto plano?