Estoy trabajando en una aplicación en ASP.NET y me preguntaba específicamente cómo podría implementar una Password Reset
función si quisiera lanzar la mía propia.
Específicamente, tengo las siguientes preguntas:
- ¿Cuál es una buena forma de generar una identificación única que sea difícil de descifrar?
- ¿Debería haber un temporizador adjunto? Si es así, ¿cuánto tiempo debería ser?
- ¿Debo registrar la dirección IP? ¿Incluso importa?
- ¿Qué información debo solicitar en la pantalla "Restablecer contraseña"? ¿Solo dirección de correo electrónico? ¿O tal vez la dirección de correo electrónico más algún dato que 'conozcan'? (Equipo favorito, nombre del cachorro, etc.)
¿Hay otras consideraciones que deba tener en cuenta?
NB : Otras preguntas han pasado por alto la implementación técnica por completo. De hecho, la respuesta aceptada pasa por alto los detalles sangrientos. Espero que esta pregunta y las respuestas subsiguientes entren en los detalles sangrientos, y espero que al formular esta pregunta de manera mucho más estrecha, las respuestas sean menos "esponjosas" y más "gore".
Editar : Se agradecerían las respuestas que también explican cómo se modelaría y manejaría dicha tabla en SQL Server o cualquier enlace ASP.NET MVC a una respuesta.
fuente
Respuestas:
Muchas buenas respuestas aquí, no me molestaré en repetirlas todas ...
Excepto por un problema, que se repite en casi todas las respuestas aquí, aunque es incorrecto:
Esto no es cierto, los GUID son identificadores muy débiles y NO deben usarse para permitir el acceso a la cuenta de un usuario.
Si examina la estructura, obtiene un total de 128 bits como máximo ... lo que no se considera mucho hoy en día.
De los cuales la primera mitad es invariante típico (para el sistema generador), y la mitad de lo que queda depende del tiempo (o algo similar).
Con todo, es un mecanismo muy débil y fácilmente forzado.
¡Así que no uses eso!
En su lugar, simplemente use un generador de números aleatorios criptográficamente fuerte (
System.Security.Cryptography.RNGCryptoServiceProvider
) y obtenga al menos 256 bits de entropía sin procesar.Todo lo demás, como las otras numerosas respuestas proporcionadas.
fuente
EDITAR 2012/05/22: Como seguimiento de esta popular respuesta, ya no uso GUID en este procedimiento. Al igual que la otra respuesta popular, ahora uso mi propio algoritmo de hash para generar la clave para enviar la URL. Esto también tiene la ventaja de ser más corto. Mire en System.Security.Cryptography para generarlos, que generalmente también uso un SALT.
Primero, no restablezca inmediatamente la contraseña del usuario.
Primero, no restablezca inmediatamente la contraseña del usuario cuando lo soliciten. Esta es una brecha de seguridad, ya que alguien podría adivinar direcciones de correo electrónico (es decir, su dirección de correo electrónico en la empresa) y restablecer las contraseñas a su antojo. Las mejores prácticas en estos días generalmente incluyen un enlace de "confirmación" enviado a la dirección de correo electrónico del usuario, confirmando que desean restablecerla. Este enlace es donde desea enviar el enlace de clave única. Envío el mío con un enlace como:
domain.com/User/PasswordReset/xjdk2ms92
Sí, establezca un tiempo de espera en el enlace y almacene la clave y el tiempo de espera en su backend (y sal si está usando uno). Los tiempos de espera de 3 días son la norma, y asegúrese de notificar al usuario de 3 días a nivel web cuando soliciten reiniciar.
Utilice una clave hash única
Mi respuesta anterior decía usar un GUID. Ahora estoy editando esto para aconsejar a todos que usen un hash generado aleatoriamente, por ejemplo, usando el
RNGCryptoServiceProvider
. Y asegúrese de eliminar las "palabras reales" del hash. Recuerdo una llamada telefónica especial a las 6 am en la que una mujer recibió cierta palabra "c" en su clave hash "se supone que sea aleatorio" que hizo un desarrollador. Doh!Todo el procedimiento
RNGCryptoServiceProvider
, lo almacena como una entidad separada en unaut_UserPasswordRequests
tabla y lo vincula al usuario. Así que esto para que pueda rastrear solicitudes antiguas e informar al usuario que los enlaces más antiguos han expirado.El usuario obtiene el enlace, me gusta
http://domain.com/User/PasswordReset/xjdk2ms92
y hace clic en él.Si se verifica el enlace, solicita una nueva contraseña. Simple, y el usuario puede establecer su propia contraseña. O establezca su propia contraseña críptica aquí e infórmeles de su nueva contraseña aquí (y envíelos por correo electrónico).
fuente
Primero, necesitamos saber lo que ya sabe sobre el usuario. Obviamente, tienes un nombre de usuario y una contraseña antigua. ¿Qué más sabes? ¿Tienes dirección de correo electrónico? ¿Tienes datos sobre la flor favorita del usuario?
Suponiendo que tiene un nombre de usuario, una contraseña y una dirección de correo electrónico que funcione, debe agregar dos campos a su tabla de usuario (asumiendo que es una tabla de base de datos): una fecha llamada new_passwd_expire y una cadena new_passwd_id.
Suponiendo que tiene la dirección de correo electrónico del usuario, cuando alguien solicita un restablecimiento de contraseña, actualiza la tabla de usuarios de la siguiente manera:
new_passwd_expire = now() + some number of days new_passwd_id = some random string of characters (see below)
A continuación, envía un correo electrónico al usuario en esa dirección:
Ahora, codificando yourscript.lang: este script necesita un formulario. Si la actualización de var pasó a la URL, el formulario solo solicita el nombre de usuario y la dirección de correo electrónico del usuario. Si la actualización no se aprueba, solicita el nombre de usuario, la dirección de correo electrónico y el código de identificación enviado en el correo electrónico. También solicita una nueva contraseña (dos veces, por supuesto).
Para verificar la nueva contraseña del usuario, verifica que el nombre de usuario, la dirección de correo electrónico y el código de identificación coincidan, que la solicitud no haya expirado y que las dos nuevas contraseñas coincidan. Si tiene éxito, cambie la contraseña del usuario a la nueva contraseña y borre los campos de restablecimiento de contraseña de la tabla de usuarios. También asegúrese de cerrar la sesión del usuario / borrar las cookies relacionadas con el inicio de sesión y redirigir al usuario a la página de inicio de sesión.
Básicamente, el campo new_passwd_id es una contraseña que solo funciona en la página de restablecimiento de contraseña.
Una mejora potencial: podría eliminar <nombre de usuario> del correo electrónico. "Alguien ha solicitado un restablecimiento de contraseña para una cuenta en esta dirección de correo electrónico ..." Por lo tanto, el nombre de usuario es algo que solo el usuario sabe si el correo electrónico es interceptado. No comencé de esa manera porque si alguien está atacando la cuenta, ya conoce el nombre de usuario. Esta oscuridad adicional detiene los ataques de oportunidad de intermediarios en caso de que alguien malintencionado intercepte el correo electrónico.
En cuanto a tus preguntas:
Generando la cadena aleatoria: no necesita ser extremadamente aleatorio. Cualquier generador de GUID o incluso md5 (concat (salt, current_timestamp ())) es suficiente, donde salt es algo en el registro del usuario como se creó la cuenta de marca de tiempo. Tiene que ser algo que el usuario no pueda ver.
timer: Sí, lo necesitas solo para mantener tu base de datos sana. No es realmente necesario más de una semana, pero al menos 2 días, ya que nunca se sabe cuánto puede durar un retraso de correo electrónico.
Dirección IP: dado que el correo electrónico puede demorarse días, la dirección IP solo es útil para el registro, no para la validación. Si desea registrarlo, hágalo, de lo contrario no lo necesita.
Pantalla de reinicio: ver arriba.
Espero que lo cubra. Buena suerte.
fuente
Es probable que un GUID enviado a la dirección de correo electrónico registrada sea suficiente para la mayoría de las aplicaciones corrientes, con un tiempo de espera aún mejor.
Después de todo, si el buzón de correo electrónico del usuario se ha visto comprometido (es decir, un pirata informático tiene el inicio de sesión / contraseña para la dirección de correo electrónico), no hay mucho que pueda hacer al respecto.
fuente
Puede enviar un correo electrónico al usuario con un enlace. Este enlace contendría una cadena difícil de adivinar (como GUID). En el lado del servidor, también almacenaría la misma cadena que envió al usuario. Ahora, cuando el usuario presiona el enlace, puede encontrar en su entrada de base de datos la misma cadena secreta y restablecer su contraseña.
fuente
1) Para generar la identificación única, puede usar Secure Hash Algorithm. 2) temporizador adjunto? ¿Quiso decir un vencimiento para el enlace de restablecimiento de pwd? Sí, puede tener un conjunto de Caducidad 3) Puede solicitar más información además del ID de correo electrónico para validar ... Como la fecha de nacimiento o algunas preguntas de seguridad 4) También puede generar caracteres aleatorios y solicitar ingresarlos junto con la solicitud .. para asegurarse de que la solicitud de contraseña no esté automatizada por algún software espía o cosas por el estilo ..
fuente
Creo que la guía de Microsoft para ASP.NET Identity es un buen comienzo.
https://docs.microsoft.com/en-us/aspnet/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity
Código que uso para ASP.NET Identity:
Web.Config:
<add key="AllowedHosts" value="example.com,example2.com" />
AccountController.cs:
[Route("RequestResetPasswordToken/{email}/")] [HttpGet] [AllowAnonymous] public async Task<IHttpActionResult> GetResetPasswordToken([FromUri]string email) { if (!ModelState.IsValid) return BadRequest(ModelState); var user = await UserManager.FindByEmailAsync(email); if (user == null) { Logger.Warn("Password reset token requested for non existing email"); // Don't reveal that the user does not exist return NoContent(); } //Prevent Host Header Attack -> Password Reset Poisoning. //If the IIS has a binding to accept connections on 80/443 the host parameter can be changed. //See https://security.stackexchange.com/a/170759/67046 if (!ConfigurationManager.AppSettings["AllowedHosts"].Split(',').Contains(Request.RequestUri.Host)) { Logger.Warn($"Non allowed host detected for password reset {Request.RequestUri.Scheme}://{Request.Headers.Host}"); return BadRequest(); } Logger.Info("Creating password reset token for user id {0}", user.Id); var host = $"{Request.RequestUri.Scheme}://{Request.Headers.Host}"; var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id); var callbackUrl = $"{host}/resetPassword/{HttpContext.Current.Server.UrlEncode(user.Email)}/{HttpContext.Current.Server.UrlEncode(token)}"; var subject = "Client - Password reset."; var body = "<html><body>" + "<h2>Password reset</h2>" + $"<p>Hi {user.FullName}, <a href=\"{callbackUrl}\"> please click this link to reset your password </a></p>" + "</body></html>"; var message = new IdentityMessage { Body = body, Destination = user.Email, Subject = subject }; await UserManager.EmailService.SendAsync(message); return NoContent(); } [HttpPost] [Route("ResetPassword/")] [AllowAnonymous] public async Task<IHttpActionResult> ResetPasswordAsync(ResetPasswordRequestModel model) { if (!ModelState.IsValid) return NoContent(); var user = await UserManager.FindByEmailAsync(model.Email); if (user == null) { Logger.Warn("Reset password request for non existing email"); return NoContent(); } if (!await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", model.Token, UserManager, user)) { Logger.Warn("Reset password requested with wrong token"); return NoContent(); } var result = await UserManager.ResetPasswordAsync(user.Id, model.Token, model.NewPassword); if (result.Succeeded) { Logger.Info("Creating password reset token for user id {0}", user.Id); const string subject = "Client - Password reset success."; var body = "<html><body>" + "<h1>Your password for Client was reset</h1>" + $"<p>Hi {user.FullName}!</p>" + "<p>Your password for Client was reset. Please inform us if you did not request this change.</p>" + "</body></html>"; var message = new IdentityMessage { Body = body, Destination = user.Email, Subject = subject }; await UserManager.EmailService.SendAsync(message); } return NoContent(); } public class ResetPasswordRequestModel { [Required] [Display(Name = "Token")] public string Token { get; set; } [Required] [Display(Name = "Email")] public string Email { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 10)] [DataType(DataType.Password)] [Display(Name = "New password")] public string NewPassword { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm new password")] [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] public string ConfirmPassword { get; set; } }
fuente