Restablecer contraseña mediante correo electrónico con PHP y MySQL

Stay hungry, stay foolish

Steve Jobs

En esta ocasión vamos a crear un script que nos permita restablecer la contraseña de usuarios mediante correo electrónico.

Para este ejercicio vamos a trabajar con la siguiente base de datos:

--
-- Estructura de tabla para la tabla `users`
--
CREATE TABLE IF NOT EXISTS `tblreseteopass` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`idusuario` int(10) unsigned NOT NULL,
`username` varchar(15) NOT NULL,
`token` varchar(64) NOT NULL,
`creado` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idusuario` (`idusuario`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

--
-- Estructura de tabla para la tabla `users`
--

CREATE TABLE IF NOT EXISTS `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`nombre` varchar(60) NOT NULL,
`username` varchar(15) NOT NULL,
`password` varchar(64) NOT NULL,
`email` varchar(70) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

Y generaremos los siguientes archivos:

  • index.html – Formulario para solicitar el cambio de contraseña mediante el correo electrónico.
  • validaremail.php – Script que valida y envía por correo electrónico el link para resetear la contraseña.
  • restablecer.php – Script que valida el token y muestra al usuario el formulario para resetear la contraseña.
  • cambiarpassword.php – Script que actualiza la información de la contraseña en la base de datos.

 

Archivo: index.html

Lo primero que vamos a hacer es generar el formulario donde pida el correo electrónico de la cuenta que se desea restablecer. El cual solo va a contener un input de tipo text para que el usuario escriba su correo. Adicional a esto vamos a crear un div con el atributo id=”mensaje” dentro del cual mostraremos una leyenda que le indique al usuario que el correo para restablecer su contraseña fue enviado.

    <form id="frmRestablecer" action="validaremail.php" method="post">
      <div class="panel panel-default">
        <div class="panel-heading"> Restaurar contraseña </div>
        <div class="panel-body">
          <div class="form-group">
            <label for="email"> Escribe el email asociado a tu cuenta para recuperar tu contraseña </label>
            <input type="email" id="email" class="form-control" name="email" required>
          </div>
          <div class="form-group">
            <input type="submit" class="btn btn-primary" value="Recuperar contraseña" >
          </div>
        </div>
      </div>
    </form>

    <div id="mensaje"></div>

Para enviar el formulario vamos a utilizar ajax de jquery, y lo hacemos de la siguiente forma: capturamos el evento submit del formulario y evitamos que lo envie con event.preventDefault(), en su lugar serializamos el formulario y lo enviamos por ajax al script validaremail.php

    <script>
      $(document).ready(function(){
        $("#frmRestablecer").submit(function(event){
          event.preventDefault();
          $.ajax({
            url:'validaremail.php',
            type:'post',
            dataType:'json',
            data:$("#frmRestablecer").serializeArray()
          }).done(function(respuesta){
            $("#mensaje").html(respuesta.mensaje);
            $("#email").val('');
          });
        });
      });
    </script>

Archivo: validaremail.php

En el archivo validaremail.php definimos 2 funciones, una que nos genere el link para la descarga del archivo y otra para que envíe el correo electrónico.

function generarLinkTemporal($idusuario, $username){
   // Se genera una cadena para validar el cambio de contraseña
   $cadena = $idusuario.$username.rand(1,9999999).date('Y-m-d');
   $token = sha1($cadena);

   $conexion = new mysqli('localhost', 'root', '', 'ejemplobd');
   // Se inserta el registro en la tabla tblreseteopass
   $sql = "INSERT INTO tblreseteopass (idusuario, username, token, creado) VALUES($idusuario,'$username','$token',NOW());";
   $resultado = $conexion->query($sql);
   if($resultado){
      // Se devuelve el link que se enviara al usuario
      $enlace = $_SERVER["SERVER_NAME"].'/pass/restablecer.php?idusuario='.sha1($idusuario).'&token='.$token;
      return $enlace;
   }
   else
      return FALSE;
}

function enviarEmail( $email, $link ){
   $mensaje = '<html>
     <head>
        <title>Restablece tu contraseña</title>
     </head>
     <body>
       <p>Hemos recibido una petición para restablecer la contraseña de tu cuenta.</p>
       <p>Si hiciste esta petición, haz clic en el siguiente enlace, si no hiciste esta petición puedes ignorar este correo.</p>
       <p>
         <strong>Enlace para restablecer tu contraseña</strong><br>
         <a href="'.$link.'"> Restablecer contraseña </a>
       </p>
     </body>
    </html>';

   $cabeceras = 'MIME-Version: 1.0' . "\r\n";
   $cabeceras .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
   $cabeceras .= 'From: Codedrinks <mimail@codedrinks.com>' . "\r\n";
   // Se envia el correo al usuario
   mail($email, "Recuperar contraseña", $mensaje, $cabeceras);
}

Ya que hemos definido las dos funciones que vamos a utilizar continuamos con el código que valida el correo electrónico, verificamos que no este vacío y hacemos una consulta a la base de datos para verificar si el correo existe. En caso de que nos devuelva algún registro llamamos a la función generarLinkTemporal la cual nos devolverá el link  para que el usuario pueda resetear la contraseña, el cual pasaremos a la función enviarEmail para que envíe la información al correo del usuario. Al final devolvemos una respuesta mediante json

$email = $_POST['email'];

$respuesta = new stdClass();

if( $email != "" ){
   $conexion = new mysqli('localhost', 'root', '', 'ejemplobd');
   $sql = " SELECT * FROM users WHERE email = '$email' ";
   $resultado = $conexion->query($sql);
   if($resultado->num_rows > 0){
      $usuario = $resultado->fetch_assoc();
      $linkTemporal = generarLinkTemporal( $usuario['id'], $usuario['username'] );
      if($linkTemporal){
        enviarEmail( $email, $linkTemporal );
        $respuesta->mensaje = '<div class="alert alert-info"> Un correo ha sido enviado a su cuenta de email con las instrucciones para restablecer la contraseña </div>';
      }
   }
   else
      $respuesta->mensaje = '<div class="alert alert-warning"> No existe una cuenta asociada a ese correo. </div>';
}
else
   $respuesta->mensaje= "Debes introducir el email de la cuenta";
 echo json_encode( $respuesta );

Archivo: restablecer.php

El archivo restablecer.php se encarga de verificar que el link para cambiar la contraseña sea correcto, por lo tanto lo primero que hacemos es obtener por GET el token y el idusuario para después verificar que se encuentre en la tabla tblreseteopass. De ser así se muestra el formulario para cambiar la contraseña en caso contrario se redirecciona hacia la pagina index.html

<?php
$token = $_GET['token'];
$idusuario = $_GET['idusuario'];

$conexion = new mysqli('localhost', 'root', '', 'ejemplobd');

$sql = "SELECT * FROM tblreseteopass WHERE token = '$token'";
$resultado = $conexion->query($sql);

if( $resultado->num_rows > 0 ){
   $usuario = $resultado->fetch_assoc();
   if( sha1($usuario['idusuario']) == $idusuario ){
?>
<!DOCTYPE html>
<html lang="es">
 <head>
  <meta name="author" content="denker">
  <title> Restablecer contraseña </title>
  <link href="css/bootstrap.min.css" rel="stylesheet">
  <link href="css/bootstrap-theme.min.css" rel="stylesheet">
  <link href="css/style.css" rel="stylesheet">
 </head>

 <body>
  <div class="container" role="main">
   <div class="col-md-4"></div>
   <div class="col-md-4">
    <form action="cambiarpassword.php" method="post">
     <div class="panel panel-default">
      <div class="panel-heading"> Restaurar contraseña </div>
      <div class="panel-body">
       <p></p>
       <div class="form-group">
        <label for="password"> Nueva contraseña </label>
        < input type="password" class="form-control" name="password1" required>
       </div>
       <div class="form-group">
        <label for="password2"> Confirmar contraseña </label>
        <input type="password" class="form-control" name="password2" required>
       </div>
       <input type="hidden" name="token" value="<?php echo $token ?>">
       <input type="hidden" name="idusuario" value="<?php echo $idusuario ?>">
       <div class="form-group">
        <input type="submit" class="btn btn-primary" value="Recuperar contraseña" >
       </div>
      </div>
     </div>
    </form>
   </div>
  <div class="col-md-4"></div>
  </div> <!-- /container -->

  <script src="js/jquery-1.11.1.js"></script>
  <script src="js/bootstrap.min.js"></script>
 </body>
</html>
<?php
   }
   else{
     header('Location:index.html');
   }
 }
 else{
     header('Location:index.html');
 }
?>

Archivo: cambiarpassword.php

El archivo cambiarpassword.php se encarga de recibir las contraseñas, el idusuario y el token para actualizar la información en la base de datos, por lo tanto primero se verifica que se hayan recibido todos los datos posteriormente se realiza una consulta para verificar si el token existe, que las contraseñas coincidan y posteriormente se actualiza la tabla users con la nueva contraseña, al final se elimina el registro de la tabla: tblreseteopass.


<?php
$password1 = $_POST['password1'];
$password2 = $_POST['password2'];
$idusuario = $_POST['idusuario'];
$token = $_POST['token'];

if( $password1 != "" && $password2 != "" && $idusuario != "" && $token != "" ){
?>
<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title> Restablecer contraseña </title>
    <link href="css/bootstrap.min.css" rel="stylesheet">
    <link href="css/bootstrap-theme.min.css" rel="stylesheet">
    <link href="css/style.css" rel="stylesheet">
  </head>

  <body>
    <div class="container" role="main">
      <div class="col-md-2"></div>
      <div class="col-md-8">
<?php
   $conexion = new mysqli('localhost', 'root', '', 'ejemplobd');
   $sql = " SELECT * FROM tblreseteopass WHERE token = '$token' ";
   $resultado = $conexion->query($sql);
   if( $resultado->num_rows > 0 ){
      $usuario = $resultado->fetch_assoc();
      if( sha1( $usuario['idusuario'] === $idusuario ) ){
         if( $password1 === $password2 ){
            $sql = "UPDATE users SET password = '".sha1($password1)."' WHERE id = ".$usuario['idusuario'];
            $resultado = $conexion->query($sql);
            if($resultado){
               $sql = "DELETE FROM tblreseteopass WHERE token = '$token';";
               $resultado = $conexion->query( $sql );
?>
               <p class="alert alert-info"> La contraseña se actualizó con exito. </p>
<?php
            }
            else{
?>
              <p class="alert alert-danger"> Ocurrió un error al actualizar la contraseña, intentalo más tarde </p>
<?php
            }
         }
         else{
?>
           <p class="alert alert-danger"> Las contraseñas no coinciden </p>
<?php
         }
      }
      else{
?>
        <p class="alert alert-danger"> El token no es válido </p>
<?php
      }
   }
   else{
?>
      <p class="alert alert-danger"> El token no es válido </p>
<?php
   }
?>
</div>
<div class="col-md-2"></div>
</div> <!-- /container -->
<script src="js/jquery-1.11.1.js"></script>
<script src="js/bootstrap.min.js"></script>
</body>
</html>
<?php
}
else{
   header('Location:index.html');
}
?>

Para que el link tenga fecha de caducidad podemos agregar un evento de MySQL que cada determinado tiempo elimine los registros que han caducado. Supongamos que cada 12 horas queremos que se ejecute el código que elimina los registros que fueron creados hace mas de dos días el evento quedaría de la siguiente forma:

CREATE EVENT mievento
   ON SCHEDULE EVERY 12 HOUR
      CURRENT_TIMESTAMP
   DO
      DELETE FROM tblreseteopass WHERE creado <= DATE_SUB(CURTIME(), INTERVAL 2 DAY)

Eso es todo, pueden moverle al código para agregar una mayor funcionalidad, Aquí les dejo el link de descarga.

download