Prompt y Contexto Aplicable
Diseñe un flujo de autoservicio de restablecimiento de contraseña mediante enlace por correo electrónico para una aplicación web de consumo. La API de solicitud acepta una dirección de correo electrónico y, cuando una cuenta coincide, envía un enlace HTTPS que contiene un token opaco. El token expira 30 minutos después de su creación. La base de datos nunca debe almacenar el token portador (bearer token) en texto plano.
El diseño debe evitar que un llamador no autenticado descubra si un correo electrónico está registrado, sature una bandeja de entrada, reproduzca un enlace usado, cambie la contraseña de otro usuario o gane una carrera contra un segundo envío de restablecimiento. Un restablecimiento exitoso cambia la contraseña, invalida todos los tokens de restablecimiento pendientes para esa cuenta, revoca las sesiones autenticadas existentes y emite una notificación de seguridad. El restablecimiento de contraseña no elimina ni reemplaza un autenticador MFA registrado.
Esta es primordialmente una pregunta de seguridad y consistencia en el backend. Una respuesta pulida conecta el modelo de amenazas con el contrato de la API, el ciclo de vida del token, la transacción de la base de datos, la entrega de correo electrónico, el modelo de sesiones y la evidencia operativa. Nombrar un token aleatorio y un tiempo de expiración es solo el comienzo; la parte difícil radica en lograr que cada ruta observable y concurrente obedezca el mismo contrato de seguridad.
Qué Evalúa el Entrevistador
La primera señal es el modelado de amenazas. El candidato debe identificar la enumeración de cuentas, el bombardeo de correos de restablecimiento, la inyección del encabezado Host, la filtración de URLs, la adivinación de tokens, la divulgación en bases de datos, la reproducción de enlaces, la manipulación de clientes, el consumo concurrente, las sesiones robadas y la evasión de MFA. Cada amenaza debe mapearse a un control específico en lugar de una afirmación genérica de que el endpoint es seguro.
La segunda señal es la distinción entre el token en texto plano y su verificador almacenado. El correo electrónico contiene un secreto portador de alta entropía. La base de datos almacena únicamente un resumen criptográfico (digest) unidireccional, por lo que leer la tabla de tokens no revela directamente un enlace utilizable. El candidato también debe distinguir este token aleatorio de una contraseña humana: una contraseña elegida por el usuario necesita un hash de contraseñas lento, mientras que un token de 256 bits uniformemente aleatorio puede indexarse mediante un digest criptográfico rápido sin volverse predecible.
La tercera señal es el diseño de transacciones. Verificar used_at en el código de la aplicación y actualizarlo más tarde genera una condición de carrera por reproducción. Dos tokens válidos distintos para el mismo usuario generan otra carrera a menos que la cuenta sea el punto de serialización. Una respuesta sólida bloquea la fila del usuario, consume condicionalmente el token presentado, cambia la contraseña, revoca los tokens hermanos y las sesiones, y registra un evento outbox dentro de una única transacción corta.
La cuarta señal es el criterio sobre los límites de recuperación. Restablecer una contraseña olvidada no es un permiso para desactivar MFA o reemplazar un segundo factor perdido. La recuperación de cuentas de mayor garantía necesita una ruta de verificación diseñada por separado. La respuesta también debe rechazar las preguntas de seguridad como único mecanismo de recuperación y evitar enviar tanto la contraseña antigua como una recién generada por correo electrónico.
La señal final es el pensamiento operativo. Las respuestas genéricas son ineficaces si los tiempos de respuesta, el comportamiento del límite de tasa (rate limit), los registros, la analítica o las herramientas de soporte aún divulgan la existencia de cuentas o tokens en bruto. La entrega de correo electrónico debe sobrevivir a fallos del proveedor sin modificar la cuenta prematuramente, y el monitoreo debe detectar abusos sin almacenar el secreto portador en los registros.
Preguntas para Aclarar Antes de Responder
- ¿Es esto un reemplazo de contraseña o una recuperación completa de la cuenta? Si el usuario aún tiene un correo electrónico verificado pero solo perdió la contraseña, el enlace puede reemplazar esa contraseña. Perder el acceso al correo electrónico, MFA y códigos de recuperación requiere un proceso de recuperación independiente y más robusto.
- ¿La cuenta utiliza MFA? Un restablecimiento de contraseña debe dejar intactos los factores registrados. Si el producto busca un único flujo para recuperar ambos, sus requisitos de garantía y revisión de abusos cambian sustancialmente.
- ¿Qué sesiones deben revocarse? Este prompt revoca todas las sesiones. Mantener la sesión iniciada en el dispositivo solicitante requeriría demostrar que ya era confiable y una excepción claramente documentada.
- ¿Pueden existir varios enlaces pendientes? Permitir un número acotado evita invalidar un correo legítimo simplemente porque un mensaje posterior llegó primero. El enlace que tenga éxito debe revocar todos los demás para ese usuario.
- ¿Qué canales de riesgo y entrega aplican? El correo electrónico puede ser aceptable para una cuenta de consumo de bajo riesgo, pero insuficiente para accesos financieros o regulados. Los SMS, códigos de recuperación, validación por soporte y períodos de espera conllevan diferentes riesgos de apropiación y disponibilidad.
- ¿Qué política de contraseñas rige actualmente el inicio de sesión? El restablecimiento debe usar las mismas políticas de longitud, listas de bloqueo y hashing. Una política específica de restablecimiento más débil se convierte en un mecanismo de evasión de autenticación.
- ¿Qué presupuesto de abuso y límites del proveedor de correo existen? Los límites de tasa necesitan dimensiones como cuenta, IP, dispositivo y capacidad global del proveedor. Un bloqueo estricto solo por cuenta permite que un atacante impida la recuperación a una víctima conocida.
Estructura de Respuesta en 30 Segundos
“Devolvería la misma respuesta 202 y el mismo mensaje para cada correo electrónico, realizando luego la búsqueda y entrega de forma asíncrona tras límites de tasa por capas. Para una cuenta real, generaría 32 bytes aleatorios, enviaría por correo el token en base64url dentro de un enlace construido a partir de un origen HTTPS configurado, y almacenaría únicamente su digest junto con el usuario y una expiración de 30 minutos. La página GET nunca consume el token. En el POST final, lo revalido, calculo el hash de la nueva contraseña, bloqueo la fila del usuario, marco atómicamente el token como usado, actualizo la contraseña, revoco todos los demás tokens de restablecimiento y sesiones, y escribo los eventos outbox de auditoría y notificación. Las solicitudes concurrentes o reproducidas fallan entonces el consumo condicional. La recuperación de MFA se mantiene como un flujo separado.”
Análisis Detallado Paso a Paso
Comience con dos endpoints públicos y contratos de respuesta deliberadamente pequeños:
POST /password-reset-requests
{ "email": "person@example.com" }
202 Accepted
{ "message": "If an account matches, reset instructions will be sent." }
POST /password-resets
{ "token": "opaque-base64url-value", "new_password": "..." }El endpoint de solicitud devuelve el mismo estado, estructura de mensaje y política de caché independientemente de si la cuenta existe. Debe transferir el trabajo a una cola acotada antes de responder para que una salida rápida en la base de datos o una llamada SMTP no generen un oráculo de tiempo (timing oracle). No agregue una pausa (sleep) fija asumiendo resuelto el problema: la saturación de la cola, las limitaciones exclusivas por cuenta y las diferentes rutas de error aún pueden exponer el comportamiento o convertirse en una herramienta de denegación de servicio.
Normalice el correo electrónico únicamente de acuerdo con las reglas de identidad verificada del producto. Convertir un dominio a minúsculas puede ser válido; aplicar reglas específicas de un proveedor (como eliminar puntos o etiquetas 'plus') a todos los dominios puede fusionar dos cuentas distintas. Aplique límites de tasa en capas: por clave de cuenta normalizada, IP o red de origen, señal de riesgo o dispositivo, y el presupuesto global del proveedor de correo electrónico. Escale el tráfico sospechoso a un desafío (challenge). La respuesta pública permanece genérica, y una solicitud de contraseña olvidada nunca bloquea la cuenta ni cambia su contraseña.
Para una cuenta existente, genere 32 bytes con un generador aleatorio criptográficamente seguro. Eso equivale a 256 bits; base64url sin relleno lo representa en 43 caracteres. Esta es una decisión de diseño, no una regla universal de codificación o expiración. Almacene SHA-256(raw_token) y envíe el valor en bruto únicamente a través del enlace de correo electrónico. Debido a que la entrada es uniformemente aleatoria y de alta entropía, un digest unidireccional rápido permite búsquedas indexadas; usar el digest mismo como el valor portador enviado falla porque el servidor aplica el hash a la entrada nuevamente. Las contraseñas son secretos humanos de baja entropía y, por lo tanto, requieren un hash de contraseñas lento y con salting.
Una tabla ilustrativa en PostgreSQL es:
CREATE TABLE password_reset_tokens (
id uuid PRIMARY KEY,
user_id uuid NOT NULL REFERENCES users(id),
token_digest bytea NOT NULL UNIQUE,
created_at timestamptz NOT NULL,
expires_at timestamptz NOT NULL,
used_at timestamptz,
revoked_at timestamptz
);
CREATE INDEX password_reset_tokens_active_user_idx
ON password_reset_tokens (user_id, expires_at)
WHERE used_at IS NULL AND revoked_at IS NULL;La URL del correo electrónico debe utilizar un origen configurado o en lista blanca en lugar de un encabezado Host no confiable. Use HTTPS, oculte/redacte el token de los registros de la aplicación, proxies, analítica y errores, y mantenga los recursos de terceros fuera de la página de restablecimiento. Configure una política sin referente (no-referrer) para que la navegación no divulgue el token en la consulta. Un GET puede mostrar el formulario o informar que un enlace es inválido, pero no debe consumir el token: los escáneres de seguridad de correo y las vistas previas de enlaces suelen visitar los enlaces antes que el usuario.
No confíe en una validación realizada durante ese GET. Un cliente modificado puede llamar directamente al endpoint final, por lo que el POST que cambia la contraseña debe recibir y revalidar el token. Antes de abrir una transacción, obtenga el digest del token, busque un candidato no expirado, valide la nueva contraseña y calcule su hash lento. Esto mantiene el costoso trabajo de hashing de contraseñas y la mayoría de las solicitudes inválidas fuera del bloqueo de la cuenta. Aplique también un límite de tasa a este endpoint.
El cambio de estado final utiliza la fila del usuario como punto de serialización:
candidate = find token by SHA-256(raw_token)
reject publicly if candidate is missing, expired, used, or revoked
new_hash = Argon2id(new_password, fresh_salt, tuned_parameters)
BEGIN
SELECT id FROM users WHERE id = candidate.user_id FOR UPDATE
UPDATE password_reset_tokens
SET used_at = now()
WHERE id = candidate.id
AND used_at IS NULL
AND revoked_at IS NULL
AND expires_at > now()
RETURNING user_id
if no row returned: ROLLBACK and reject
UPDATE users
SET password_hash = new_hash,
password_changed_at = now(),
auth_version = auth_version + 1
WHERE id = candidate.user_id
UPDATE password_reset_tokens
SET revoked_at = now()
WHERE user_id = candidate.user_id
AND id <> candidate.id
AND used_at IS NULL
AND revoked_at IS NULL
DELETE FROM sessions WHERE user_id = candidate.user_id
INSERT security_outbox(password_reset_succeeded, user_id, occurred_at)
COMMITCalcule el hash antes de BEGIN, pero nunca lo confirme a menos que la actualización condicional del token tenga éxito. Para un nuevo despliegue, OWASP actualmente lista Argon2id con 19 MiB de memoria, dos iteraciones y un grado de paralelismo como una configuración mínima; realice pruebas de rendimiento y aumente el costo mientras mantiene segura la capacidad de autenticación legítima. Almacene el algoritmo y los parámetros junto con el hash de la contraseña para que puedan evolucionar. Bcrypt es una alternativa heredada con restricciones en la longitud de entrada, no un sinónimo directo.
El bloqueo de fila maneja dos clases de carreras. Si dos solicitudes presentan el mismo token, solo la primera actualización condicional puede establecer used_at. Si presentan dos tokens válidos distintos para un mismo usuario, ambos pueden superar la lectura inicial y el cálculo de hash, pero solo uno retendrá el bloqueo del usuario. Esa transacción revoca el otro token; una vez que la segunda solicitud adquiere el bloqueo, su actualización condicional no devuelve ninguna fila. Por lo tanto, la contraseña obtiene un único valor ganador, y cada solicitud perdedora observa un estado de token terminal.
La eliminación de sesiones en el servidor ofrece una invalidación inmediata para sesiones respaldadas en base de datos. Con los refresh tokens, revoque sus registros en el servidor. Para tokens de acceso que de otro modo serían stateless, incrementar auth_version o verificar password_changed_at funciona solo si cada solicitud protegida compara ese estado del servidor; sin dicha comprobación, la revocación se retrasa hasta la expiración del token. Haga explícita esa limitación en lugar de afirmar que borrar una cookie del navegador revoca la sesión de un atacante en otro lugar.
Escriba la notificación de éxito y el evento de auditoría de seguridad a través de una outbox en la misma transacción, entregándolos después del commit. La notificación contiene la hora, orientación sobre recuperación de cuenta y una forma de reportar fraude, nunca la contraseña antigua o la nueva. Una interrupción del proveedor de notificaciones debe activar reintentos y alertas; no debe revertir una contraseña que ya ha cambiado. En el lado de la solicitud, el token y el trabajo de correo deben estar relacionados de forma duradera para que un commit en la base de datos seguido de un fallo en la cola no deje la solicitud silenciosamente varada. Una outbox o un almacén de trabajos transaccional resuelve ese límite.
Permita una cantidad acotada de enlaces pendientes en lugar de revocar el enlace anterior en cada solicitud. El reemplazo inmediato permite a un atacante solicitar restablecimientos repetidamente e invalidar el correo de la víctima antes de que sea abierto. Limite las filas pendientes, revoque o expire la más antigua al alcanzar el límite máximo, y revoque todas las filas restantes tras el éxito. Elimine periódicamente las filas terminales y expiradas según la política de retención de auditoría.
Los tokens opacos almacenados suelen ser más simples que los enlaces de restablecimiento basados en JWT autónomos. Un JWT firmado aún requiere estado en el servidor para el uso único inmediato, la revocación a nivel de usuario y las carreras de cambio de contraseña; una vez que ese estado existe, un token aleatorio más un digest tiene menos claims y rutas de análisis (parsing). Un PIN corto es útil para el ingreso manual, pero tiene mucha menos entropía, por lo que necesita una limitación estricta de intentos y una sesión de restablecimiento acotada después de la verificación.
El monitoreo debe rastrear las tasas de solicitud por dimensión de riesgo, retraso en colas, errores del proveedor, resultados de entrega, fallos de verificación de tokens, antigüedad del token al tener éxito, intentos de reproducción y fallos en la revocación de sesiones. Mantenga los correos en bruto, tokens, nuevas contraseñas y URLs completas de restablecimiento fuera de las métricas y registros. Las alertas deben detectar tanto campañas globales como la saturación de cuentas individuales sin exponer la existencia de la cuenta en la respuesta pública.
Pruebe el flujo como una máquina de estados, no solo como un endpoint de camino feliz (happy path). Envíe solicitudes POST paralelas con el mismo token y con dos tokens diferentes para un mismo usuario; exactamente una debe tener éxito. Verifique la expiración en el límite de tiempo, la reutilización tras el éxito, la revocación de tokens hermanos, cuentas inexistentes, caídas en colas y correos, manipulación del encabezado Host, filtración de Referer, redacción de registros, dimensiones de límite de tasa, rechazo de sesiones en otro dispositivo, reintentos de notificación y cuentas con MFA. Los escáneres de seguridad deben poder hacer un GET al enlace sin consumirlo.
Ejemplo de Respuesta de Alta Calidad
“Definiría el token de restablecimiento como un autenticador temporal y diseñaría en torno a todo su ciclo de vida. El endpoint de solicitud siempre devuelve la misma respuesta 202. Pone en cola la búsqueda y la entrega detrás de controles de abuso a nivel de cuenta, red, dispositivo y proveedor, pero nunca bloquea ni modifica la cuenta simplemente porque alguien escribió una dirección de correo electrónico.
Para una cuenta coincidente, genero 32 bytes aleatorios, envío el valor base64url en un enlace HTTPS y almaceno solo su digest SHA-256, el ID de usuario y una expiración de 30 minutos. El origen de restablecimiento está configurado en lugar de derivarse de Host; la página no tiene recursos de terceros, utiliza una política no-referrer y todos los pipelines de registros redactan el token. El método GET solo muestra el formulario porque un escáner de correo electrónico podría abrir el enlace.
El POST final valida el token nuevamente y calcula el hash de la nueva contraseña antes de iniciar una transacción corta. Dentro de la transacción, bloqueo la fila del usuario, marco condicionalmente ese token como usado, actualizo la contraseña y la versión de autenticación, revoco todos los tokens de restablecimiento hermanos y las sesiones del lado del servidor, e inserto un evento outbox. La actualización condicional frustra la reproducción con el mismo token; el bloqueo de usuario más la revocación de hermanos hace que dos tokens diferentes se serialicen hacia un único ganador.
Tras el commit, los workers envían la notificación de seguridad y reintentan de forma independiente. Probaría respuestas genéricas y tiempos de respuesta, filtración de tokens y URLs, uso concurrente, expiración, fallos de proveedores, rechazo de sesiones remotas y cuentas con MFA. Restablecer la contraseña deja MFA intacto; perder todos los autenticadores pasa por un proceso de recuperación separado de mayor garantía.”
Errores Comunes
- Devolver “correo electrónico no encontrado” → un atacante puede enumerar cuentas registradas → devuelva el mismo estado y mensaje público y evite una ruta de salida rápida basada en tiempos.
- Almacenar el token en texto plano → quien lea la base de datos obtiene enlaces de restablecimiento funcionales → almacene un digest unidireccional y oculte/redacte el valor en bruto en todos los lugares excepto en la entrega y el envío del usuario.
- Usar un ID de usuario proveniente del formulario final → un cliente puede cambiar la cuenta de destino → obtenga el usuario únicamente a partir del registro del token en el servidor.
- Consumir el enlace en la petición GET → un escáner de correo puede invalidarlo antes de que llegue el usuario → consúmalo únicamente durante el POST que cambia la contraseña.
- Validar en el GET pero no en el POST → un cliente modificado evade el formulario mostrado → revalide la expiración y el estado terminal en la transacción final del lado del servidor.
- Verificar y luego actualizar sin una condición → solicitudes paralelas pueden superar ambas la comprobación → use una actualización condicional dentro de una transacción y serialice los diferentes tokens sobre la fila del usuario.
- Invalidar el enlace anterior en cada solicitud → un atacante puede mantener desactualizado el correo más reciente de la víctima → permita un conjunto acotado y revoque todos los hermanos solo después de que uno tenga éxito.
- Construir la URL a partir de
Host→ una solicitud envenenada puede enviar un dominio de restablecimiento controlado por el atacante → use un origen HTTPS configurado o en lista blanca. - Colocar tokens en registros o analítica → los sistemas de observabilidad se convierten en almacenes de credenciales → redacte las cadenas de consulta (query strings) y prohíba recursos de terceros en la página de restablecimiento.
- Iniciar sesión automáticamente tras el restablecimiento sin una política de sesiones → la fijación de sesiones y el comportamiento de sesiones robadas se vuelven confusos → requiera un inicio de sesión normal y revoque explícitamente las sesiones en el servidor.
- Restablecer MFA junto con la contraseña → el control de un correo electrónico puede comprometer un factor más fuerte → mantenga la recuperación de MFA como un flujo separado basado en riesgo.
- Enviar una nueva contraseña por correo electrónico → la contraseña persiste en un canal inseguro y un atacante puede bloquear a la víctima → envíe un enlace de tiempo limitado y no modifique nada hasta que se presente una prueba válida.
Preguntas de Seguimiento y Respuestas
Pregunta de seguimiento 1: ¿Qué cambia si el producto utiliza tokens de acceso JWT stateless?
Revoque los refresh tokens en el servidor. Para una invalidación inmediata de los tokens de acceso, incluya una versión de autenticación o el valor 'issued-at' y compárelo con el estado actual del servidor en cada solicitud protegida. Si la arquitectura descarta esa búsqueda o una caché de revocación, los tokens de acceso antiguos seguirán siendo válidos hasta su expiración; declare la exposición máxima en lugar de prometer un cierre de sesión inmediato.
Pregunta de seguimiento 2: ¿Debería una nueva solicitud invalidar todos los enlaces de restablecimiento anteriores?
Por lo general no antes de que uno tenga éxito. El correo electrónico puede retrasarse o desordenarse, y un atacante que conozca la dirección podría invalidar continuamente el enlace más reciente de la víctima. Mantenga un conjunto pequeño y acotado de tokens no expirados, controle el volumen de solicitudes y revoque todos los hermanos en la transacción exitosa de cambio de contraseña. Un producto de alto riesgo puede optar por un reemplazo más estricto, pero debe aceptar ese compromiso en la disponibilidad.
Pregunta de seguimiento 3: ¿Cómo soportaría un código de seis dígitos en lugar de un token en la URL?
Un código de seis dígitos tiene mucha menos entropía que un token de 256 bits. Vincúlelo a la cuenta y a un intento de restablecimiento de alcance limitado, imponga una restricción estricta de intentos y expiración en el servidor, y cree una sesión de restablecimiento de corta duración solo después de una verificación exitosa. No permita que un código verificado se convierta en una sesión autenticada general ni que el cliente elija el ID de usuario.
Pregunta de seguimiento 4: ¿Qué reglas de nueva contraseña aplicaría?
Use la misma política de verificación que en el inicio de sesión. Si el producto adopta la guía actual de NIST, una contraseña utilizada como factor único tiene un mínimo de 15 caracteres; una utilizada únicamente con MFA puede tener un mínimo de 8 caracteres. Permita al menos 64 caracteres, rechace valores comunes o comprometidos mediante una lista de bloqueo y no agregue reglas arbitrarias de composición o rotación periódica. Ajuste el costo del hash de contraseñas de forma independiente a estas reglas de entrada.
Pregunta de seguimiento 5: ¿Qué sucede si el usuario también perdió el acceso al correo electrónico y a MFA?
Eso corresponde a una recuperación completa de la cuenta, no a este endpoint de restablecimiento de contraseña. Utilice códigos o contactos de recuperación registrados previamente, autenticadores restantes, verificación de identidad repetida, períodos de espera y revisión manual según el riesgo de la cuenta. Los cambios en las direcciones de recuperación requieren a su vez verificación y notificaciones independientes. Nunca recurra a preguntas de seguridad fácilmente investigables como única prueba.
Pregunta de seguimiento 6: ¿Cómo verifica que la enumeración de cuentas esté realmente controlada?
Compare cuentas existentes y no existentes a través del estado, cuerpo, encabezados, comportamiento de caché, distribuciones de latencia, transiciones de límites de tasa y efectos secundarios posteriores (downstream). Realice pruebas bajo saturación de colas y fallos de proveedores, no solo en una ejecución local aislada. Revise los registros de CDN, proxies, aplicaciones, analítica y soporte en busca de identificadores y URLs completas, y confirme que los paneles de abuso expongan señales agregadas sin convertirse en un servicio de consulta sobre la existencia de cuentas.