sql >> Base de Datos >  >> RDS >> Mysql

Registro de usuario y verificación de correo electrónico PHP y MySQL

Ya escribí una entrada de blog sobre la creación de un sistema completo de registro e inicio de sesión de usuarios con PHP y MySQL, pero no incluí la verificación por correo electrónico.

Este tutorial es un poco como una actualización del tutorial anterior. Además de poder registrarse, iniciar sesión y cerrar sesión en su cuenta, al usuario también se le enviará un correo electrónico de verificación a su dirección de correo electrónico con un enlace en el que podrá hacer clic y verificar su dirección de correo electrónico.

En la mayoría de las aplicaciones que va a crear, es importante agregar una función de verificación de correo electrónico por muchas razones:es posible que desee enviar un correo electrónico más tarde al usuario y asegurarse de que lo vea; o el usuario puede olvidar su contraseña y necesitar restablecerla, y para hacer esto, necesitaremos enviarle por correo electrónico un enlace de restablecimiento de contraseña; hay muchas otras razones, pero entiendes el punto.

En este sistema que estamos construyendo hoy, los usuarios que no hayan verificado su correo electrónico no podrán realizar ciertas acciones (solo usaré una demostración simple como ver un botón. Solo los usuarios verificados podrán ver ese botón).

Para comenzar, cree un nuevo proyecto PHP llamado verificar-usuario y en esta carpeta, cree dos archivos:signup.php y login.php.

registro.php:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">

  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css" />
  <link rel="stylesheet" href="main.css">
  <title>User verification system PHP</title>
</head>
<body>
  <div class="container">
    <div class="row">
      <div class="col-md-4 offset-md-4 form-wrapper auth">
        <h3 class="text-center form-title">Register</h3>
        <form action="signup.php" method="post">
          <div class="form-group">
            <label>Username</label>
            <input type="text" name="username" class="form-control form-control-lg" value="<?php echo $username; ?>">
          </div>
          <div class="form-group">
            <label>Email</label>
            <input type="text" name="email" class="form-control form-control-lg" value="<?php echo $email; ?>">
          </div>
          <div class="form-group">
            <label>Password</label>
            <input type="password" name="password" class="form-control form-control-lg">
          </div>
          <div class="form-group">
            <label>Password Confirm</label>
            <input type="password" name="passwordConf" class="form-control form-control-lg">
          </div>
          <div class="form-group">
            <button type="submit" name="signup-btn" class="btn btn-lg btn-block">Sign Up</button>
          </div>
        </form>
        <p>Already have an account? <a href="login.php">Login</a></p>
      </div>
    </div>
  </div>
</body>
</html>

Es solo un simple archivo HTML/CSS. Lo único que vale la pena señalar es que estamos usando el marco CSS de Bootstrap 4 para diseñar nuestra página. Puede usar cualquier otro marco de estilo de su elección o escribir su propio CSS si lo desea.

Inmediatamente después de Bootstrap CSS, incluimos un archivo main.css para un estilo personalizado. Vamos a crear ese archivo ahora. En la carpeta raíz de la aplicación, cree un archivo llamado main.css.

principal.css:

@import url('https://fonts.googleapis.com/css?family=Lora');
li { list-style-type: none; }
.form-wrapper {
  margin: 50px auto 50px;
  font-family: 'Lora', serif;
  font-size: 1.09em;
}
.form-wrapper.login { margin-top: 120px; }
.form-wrapper p { font-size: .8em; text-align: center; }
.form-control:focus { box-shadow: none; }
.form-wrapper {
  border: 1px solid #80CED7;
  border-radius: 5px;
  padding: 25px 15px 0px 15px;
}
.form-wrapper.auth .form-title { color: #007EA7; }
.home-wrapper button,
.form-wrapper.auth button {
  background: #007EA7;
  color: white;
}
.home-wrapper {
  margin-top: 150px;
  border-radius: 5px;
  padding: 10px;
  border: 1px solid #80CED7;
}

En la primera línea de este archivo estamos importando y usando algunas fuentes de Google para hacer que nuestras fuentes se vean más hermosas.

Ahora ve al archivo login.php y haz algo similar.

iniciar sesión.php:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css" />
  <link rel="stylesheet" href="main.css">
  <title>User verification system PHP - Login</title>
</head>
<body>
  <div class="container">
    <div class="row">
      <div class="col-md-4 offset-md-4 form-wrapper auth login">
        <h3 class="text-center form-title">Login</h3>
        <form action="login.php" method="post">
          <div class="form-group">
            <label>Username or Email</label>
            <input type="text" name="username" class="form-control form-control-lg" value="<?php echo $username; ?>">
          </div>
          <div class="form-group">
            <label>Password</label>
            <input type="password" name="password" class="form-control form-control-lg">
          </div>
          <div class="form-group">
            <button type="submit" name="login-btn" class="btn btn-lg btn-block">Login</button>
          </div>
        </form>
        <p>Don't yet have an account? <a href="signup.php">Sign up</a></p>
      </div>
    </div>
  </div>
</body>
</html>

En su navegador, vaya a http://localhost/cwa/verify-user/signup.php y verá un hermoso formulario de registro (lo mismo para iniciar sesión). Ignore los errores en los campos de entrada, lo arreglaremos pronto.

Por ahora, configuremos la base de datos. Cree una base de datos llamada verificar-usuario y en esta base de datos, cree una tabla de usuarios con los siguientes atributos:

CREATE TABLE `users` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `username` varchar(100) NOT NULL,
 `email` varchar(100) NOT NULL,
 `verified` tinyint(1) NOT NULL DEFAULT '0',
 `token` varchar(255) DEFAULT NULL,
 `password` varchar(255) NOT NULL,
 PRIMARY KEY (`id`)
)

Nada inusual excepto, quizás, el token y los campos verificados, que explicaré en un momento.

Ahora comenzamos con la lógica de registro real. Normalmente me gusta referirme a la parte lógica de mi aplicación como los controladores y eso es lo que haré aquí. En la carpeta raíz del proyecto, cree una carpeta llamada controladores y dentro de los controladores, cree un archivo llamado authController.php.

controladores/authController.php:

<?php
session_start();
$username = "";
$email = "";
$errors = [];

$conn = new mysqli('localhost', 'root', '', 'verify-user');

// SIGN UP USER
if (isset($_POST['signup-btn'])) {
    if (empty($_POST['username'])) {
        $errors['username'] = 'Username required';
    }
    if (empty($_POST['email'])) {
        $errors['email'] = 'Email required';
    }
    if (empty($_POST['password'])) {
        $errors['password'] = 'Password required';
    }
    if (isset($_POST['password']) && $_POST['password'] !== $_POST['passwordConf']) {
        $errors['passwordConf'] = 'The two passwords do not match';
    }

    $username = $_POST['username'];
    $email = $_POST['email'];
    $token = bin2hex(random_bytes(50)); // generate unique token
    $password = password_hash($_POST['password'], PASSWORD_DEFAULT); //encrypt password

    // Check if email already exists
    $sql = "SELECT * FROM users WHERE email='$email' LIMIT 1";
    $result = mysqli_query($conn, $sql);
    if (mysqli_num_rows($result) > 0) {
        $errors['email'] = "Email already exists";
    }

    if (count($errors) === 0) {
        $query = "INSERT INTO users SET username=?, email=?, token=?, password=?";
        $stmt = $conn->prepare($query);
        $stmt->bind_param('ssss', $username, $email, $token, $password);
        $result = $stmt->execute();

        if ($result) {
            $user_id = $stmt->insert_id;
            $stmt->close();

            // TO DO: send verification email to user
            // sendVerificationEmail($email, $token);

            $_SESSION['id'] = $user_id;
            $_SESSION['username'] = $username;
            $_SESSION['email'] = $email;
            $_SESSION['verified'] = false;
            $_SESSION['message'] = 'You are logged in!';
            $_SESSION['type'] = 'alert-success';
            header('location: index.php');
        } else {
            $_SESSION['error_msg'] = "Database error: Could not register user";
        }
    }
}

// LOGIN
if (isset($_POST['login-btn'])) {
    if (empty($_POST['username'])) {
        $errors['username'] = 'Username or email required';
    }
    if (empty($_POST['password'])) {
        $errors['password'] = 'Password required';
    }
    $username = $_POST['username'];
    $password = $_POST['password'];

    if (count($errors) === 0) {
        $query = "SELECT * FROM users WHERE username=? OR email=? LIMIT 1";
        $stmt = $conn->prepare($query);
        $stmt->bind_param('ss', $username, $password);

        if ($stmt->execute()) {
            $result = $stmt->get_result();
            $user = $result->fetch_assoc();
            if (password_verify($password, $user['password'])) { // if password matches
                $stmt->close();

                $_SESSION['id'] = $user['id'];
                $_SESSION['username'] = $user['username'];
                $_SESSION['email'] = $user['email'];
                $_SESSION['verified'] = $user['verified'];
                $_SESSION['message'] = 'You are logged in!';
                $_SESSION['type'] = 'alert-success';
                header('location: index.php');
                exit(0);
            } else { // if password does not match
                $errors['login_fail'] = "Wrong username / password";
            }
        } else {
            $_SESSION['message'] = "Database error. Login failed!";
            $_SESSION['type'] = "alert-danger";
        }
    }
}

Si ha seguido mis tutoriales anteriores, nada en este archivo debería ser nuevo para usted. Pero por el bien de los novatos, voy a explicar un poco.

Lo primero es que estamos iniciando la sesión usando session_start() ya que necesitaremos almacenar la información del usuario conectado en la sesión. Después de iniciar la sesión, estamos inicializando las variables $username y $email que estamos usando en nuestros formularios, y también la matriz $errors que contendrá los errores de validación de nuestro formulario.

A continuación, nos conectamos a la base de datos. Las siguientes dos declaraciones if que siguen son, respectivamente, el código que se ejecuta cuando el usuario hace clic en los botones de registro o inicio de sesión. En el caso de alta, comprobamos que todos los campos obligatorios han sido rellenados correctamente y solo entonces procedemos a guardar el usuario en la base de datos. También generamos un token (una cadena aleatoria única) y lo guardamos con el usuario como un atributo. Esto se utilizará para verificar el correo electrónico del usuario. Más sobre esto más adelante.

Dado que nuestro archivo authController.php es responsable del registro y el inicio de sesión, debemos incluirlo en la parte superior de las páginas signup.php y login.php porque ahí es donde se envían los datos del formulario. Así:

signup.php y login.php (en la parte superior):

<?php include 'controllers/authController.php' ?>

Si hay algún mensaje de error en la matriz $errors, debemos mostrarlo en el formulario. Para hacer eso, agregue esta declaración if dentro de su formulario directamente debajo del título del formulario para las páginas de registro e inicio de sesión.

<!-- form title -->
<h3 class="text-center form-title">Register</h3> <!-- or Login -->

<?php if (count($errors) > 0): ?>
  <div class="alert alert-danger">
    <?php foreach ($errors as $error): ?>
    <li>
      <?php echo $error; ?>
    </li>
    <?php endforeach;?>
  </div>
<?php endif;?>

Si no hay errores, nuestro script procederá a guardar el usuario en la base de datos. Después de guardar al usuario en la base de datos, lo iniciamos sesión inmediatamente. En nuestro caso, iniciar sesión en un usuario significa almacenar sus datos en la sesión y eso es lo que acabamos de hacer.

En este punto, ya puede registrarse e incluso iniciar sesión como usuario. Pero después de iniciar sesión, será redirigido a la página index.php que no existe. Lo crearemos pronto.

En authController.php, estamos almacenando el mensaje y las variables de tipo en la sesión para que se muestren tan pronto como el usuario haya iniciado sesión. El mensaje es el texto real del mensaje, mientras que el tipo es la clase de estilo Bootstrap que formateará el mensaje con colores según el valor del tipo.

Este mensaje se muestra después de que el usuario haya iniciado sesión y se muestra en el archivo index.php. Vamos a crear ese archivo ahora en la carpeta raíz de nuestro proyecto.

índice.php:

<?php include 'controllers/authController.php'?>
<?php
// redirect user to login page if they're not logged in
if (empty($_SESSION['id'])) {
    header('location: login.php');
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">

  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css" />
  <link rel="stylesheet" href="main.css">
  <title>User verification system PHP</title>
</head>

<body>
  <div class="container">
    <div class="row">
      <div class="col-md-4 offset-md-4 home-wrapper">

        <!-- Display messages -->
        <?php if (isset($_SESSION['message'])): ?>
        <div class="alert <?php echo $_SESSION['type'] ?>">
          <?php
            echo $_SESSION['message'];
            unset($_SESSION['message']);
            unset($_SESSION['type']);
          ?>
        </div>
        <?php endif;?>

        <h4>Welcome, <?php echo $_SESSION['username']; ?></h4>
        <a href="logout.php" style="color: red">Logout</a>
        <?php if (!$_SESSION['verified']): ?>
          <div class="alert alert-warning alert-dismissible fade show" role="alert">
            You need to verify your email address!
            Sign into your email account and click
            on the verification link we just emailed you
            at
            <strong><?php echo $_SESSION['email']; ?></strong>
          </div>
        <?php else: ?>
          <button class="btn btn-lg btn-primary btn-block">I'm verified!!!</button>
        <?php endif;?>
      </div>
    </div>
  </div>
</body>
</html>

Esta página está destinada a ser accesible solo para usuarios registrados. Es por eso que en la sección superior del archivo, estamos redirigiendo al usuario a la página de inicio de sesión si no ha iniciado sesión. En algún lugar en el centro de la página, estamos mostrando el mensaje. Después de mostrar el mensaje, desactivamos el mensaje y escribimos variables porque no queremos que permanezca allí en la página incluso después de que el usuario actualice la página.

Por último, en el centro de la página, realizamos una verificación para ver si el usuario que inició sesión ha verificado su dirección de correo electrónico o no. Recuerde que agregamos la variable verificada en la sesión cuando iniciamos la sesión del usuario. Si el usuario ha sido verificado, mostramos el mensaje "¡¡¡Estoy verificado!!!" botón para que lo vean. Si no están verificados, les informamos sobre el enlace de verificación que enviamos a su dirección de correo electrónico y les pedimos que hagan clic en ese enlace para verificar su correo electrónico.

Verificar correo electrónico

En el archivo authController.php, usamos un comentario para indicar dónde enviaríamos el correo electrónico de verificación al usuario llamando a sendVerificationEmail(). Vaya al archivo authController.php y descomente la llamada a la función así:

// TO DO: send verification email to user
sendVerificationEmail($email, $token);

Definiremos esta función en otro archivo e incluiremos ese archivo dentro de authController.php. Dentro de la carpeta de controladores, cree un archivo llamado sendEmails.php.

Antes de agregar cualquier código en este archivo, permítanme hablar un poco sobre PHP SwiftMailer, la biblioteca popular para enviar correos electrónicos en PHP que usaremos en este proyecto para enviar correos electrónicos desde localhost.

SwiftMailer es una biblioteca rica en funciones popular para enviar correos electrónicos en aplicaciones PHP.

Para usar Swiftmailer, primero debe instalar Composer. Una vez que haya instalado Composer, abra su terminal o línea de comando y navegue hasta la carpeta raíz del proyecto y ejecute el siguiente comando para agregar la biblioteca de Swift Mailer con todos sus archivos a nuestro proyecto:

composer require "swiftmailer/swiftmailer:^6.0"

Esto crea una carpeta de proveedor en la raíz de nuestra aplicación que contiene todo el código (clases) necesarios para enviar un correo electrónico y también crea un archivo composer.json en la raíz de la aplicación que se ve así:

{
    "require": {
        "swiftmailer/swiftmailer": "^6.0"
    }
}

Ahora abra el archivo sendEmails.php que creamos anteriormente y escribamos la función sendVerificationEmail():

<?php
require_once './vendor/autoload.php';

// Create the Transport
$transport = (new Swift_SmtpTransport('smtp.gmail.com', 465, 'ssl'))
    ->setUsername(SENDER_EMAIL)
    ->setPassword(SENDER_PASSWORD);

// Create the Mailer using your created Transport
$mailer = new Swift_Mailer($transport);

function sendVerificationEmail($userEmail, $token)
{
    global $mailer;
    $body = '<!DOCTYPE html>
    <html lang="en">

    <head>
      <meta charset="UTF-8">
      <title>Test mail</title>
      <style>
        .wrapper {
          padding: 20px;
          color: #444;
          font-size: 1.3em;
        }
        a {
          background: #592f80;
          text-decoration: none;
          padding: 8px 15px;
          border-radius: 5px;
          color: #fff;
        }
      </style>
    </head>

    <body>
      <div class="wrapper">
        <p>Thank you for signing up on our site. Please click on the link below to verify your account:.</p>
        <a href="http://localhost/cwa/verify-user/verify_email.php?token=' . $token . '">Verify Email!</a>
      </div>
    </body>

    </html>';

    // Create a message
    $message = (new Swift_Message('Verify your email'))
        ->setFrom(SENDER_EMAIL)
        ->setTo($userEmail)
        ->setBody($body, 'text/html');

    // Send the message
    $result = $mailer->send($message);

    if ($result > 0) {
        return true;
    } else {
        return false;
    }
}

La primera declaración requiere el archivo autoload.php en este archivo. Este archivo autoload.php incluirá automáticamente todas las clases de la biblioteca de Swift Mailer en la carpeta del proveedor que usamos en este archivo.

Estamos usando Gmail en este ejemplo. Por lo tanto, puede reemplazar SENDER_EMAIL y SENDER_PASSWORD con su dirección y contraseña de Gmail que desea usar como la dirección de correo electrónico del remitente. (La dirección de correo electrónico del destinatario es la que se envió a través del formulario).

Normalmente, para enviar un correo electrónico a alguien, debe iniciar sesión en su cuenta de Gmail antes de redactar el correo y enviarlo. Es el mismo tipo de cosas que hace la biblioteca de Swift Mailer. Entonces, cuando el destinatario ($userEmail) recibe el correo electrónico, es su dirección de Gmail (SENDER_EMAIL) la que verán como el correo electrónico de envío.

Ahora llamamos a la función sendVerificationEmail() en nuestro archivo authController.php pero definimos la función dentro del archivo sendEmails.php. Incluyamos el archivo sendEmails.php dentro de nuestro authController.php para que esta función esté disponible en el archivo. En la parte superior de authController.php, justo antes de session_start(), agregue la siguiente línea:

require_once 'sendEmails.php';

Eso es todo lo que necesitamos, caballeros (y damas) para enviar un correo electrónico a nuestro usuario con un enlace de verificación de correo electrónico. Pero estamos trabajando en localhost y Gmail bloqueará cualquier intento de inicio de sesión de Swift Mailer que se ejecute en localhost.

Enviar correo electrónico desde localhost

Si desea que esto se ejecute en localhost, deberá configurar su cuenta de Gmail para aceptar el inicio de sesión desde aplicaciones menos seguras. Por supuesto, esto puede presentar alguna vulnerabilidad en su cuenta de Gmail, pero solo puede hacerlo durante el breve tiempo que necesita para probar esta aplicación en localhost. Después de la prueba, puede deshacer la configuración en su cuenta de Gmail. Una vez que su aplicación haya sido alojada en Internet, estará trabajando. Solo estamos haciendo esto porque estamos en localhost.

Puede ser aún más cauteloso y crear otra cuenta de Gmail solo para fines como estos.

Así que inicie sesión en su Gmail en su navegador, vaya a https://myaccount.google.com/security#connectedapps y cambie el valor 'Permitir aplicaciones menos seguras' a ON.

Puede desactivar esto una vez que haya terminado de probar el proyecto en localhost.

Con esto, podrá enviar un correo electrónico desde localhost con un enlace de verificación una vez que el usuario se registre. Ahora mire el método sendVerificationEmail() nuevamente y notará que en el cuerpo del correo electrónico que enviamos al usuario, el token que generamos para ese usuario en particular (el token es único) se ha establecido como un parámetro en el enlace. de modo que cuando el usuario haga clic en el enlace del correo electrónico, será dirigido a nuestra aplicación en una página llamada verify_email.php con ese token en la URL. Así:

<a href="http://localhost/cwa/verify-user/verify_email.php?token=0a150966418fa3a694bcb3ab8fcacd2063a096accc0ee33c3e8c863538ee825c0b52f2e1535d0e1377558c378ba5fc3106eb">Verify Email!</a>

Entonces podemos obtener este token en nuestro verificar_email.php de esta manera (tranquilo, pronto crearemos este verificar_email.php): 

$token = $_GET['token'];

Ahora podemos usar este token para buscar al usuario que tiene este token en particular (recuerde que el token es único) y si obtenemos ese usuario, actualizamos su registro cambiando el atributo verificado a verdadero en la base de datos. Entonces, podemos decir con orgullo que hemos verificado la dirección de correo electrónico de ese usuario.

Vamos a crear este archivocheck_email.php en la carpeta raíz de nuestro proyecto:

verificar_correo.php:

<?php
session_start();

$conn = new mysqli('localhost', 'root', '', 'verify-user');

if (isset($_GET['token'])) {
    $token = $_GET['token'];
    $sql = "SELECT * FROM users WHERE token='$token' LIMIT 1";
    $result = mysqli_query($conn, $sql);

    if (mysqli_num_rows($result) > 0) {
        $user = mysqli_fetch_assoc($result);
        $query = "UPDATE users SET verified=1 WHERE token='$token'";

        if (mysqli_query($conn, $query)) {
            $_SESSION['id'] = $user['id'];
            $_SESSION['username'] = $user['username'];
            $_SESSION['email'] = $user['email'];
            $_SESSION['verified'] = true;
            $_SESSION['message'] = "Your email address has been verified successfully";
            $_SESSION['type'] = 'alert-success';
            header('location: index.php');
            exit(0);
        }
    } else {
        echo "User not found!";
    }
} else {
    echo "No token provided!";
}

Tenga en cuenta que establecer el valor del valor verificado en 1 es lo mismo que establecerlo en verdadero, ya que en la base de datos MySQL, el tipo booleano se interpreta como tinyint.

Ahora, cuando el usuario hace clic en el enlace de su correo electrónico y lo lleva a esta página, actualiza el estado verificado de ese usuario a verdadero, inicia sesión y lo redirige a la página index.php. En la página de índice, después de verificar al usuario, notará que el mensaje de advertencia que aconseja al usuario que verifique su dirección de correo electrónico ya no está y en su lugar tenemos el mensaje "¡¡¡Estoy verificado!!!" botón que solo es visible para usuarios verificados.

Una última cosa, en la página index.php después de iniciar sesión, hay un enlace de cierre de sesión que apunta a un archivo logout.php que se supone que cierra la sesión del usuario. Vamos a crear ese archivo en la raíz de nuestra aplicación:

cerrar sesión.php:

<?php
session_destroy();
unset($_SESSION['id']);
unset($_SESSION['username']);
unset($_SESSION['email']);
unset($_SESSION['verify']);
header("location: login.php");

Entonces, además de poder registrarse, verificar el correo electrónico, iniciar sesión, el usuario ahora también puede cerrar sesión.

Conclusión

Eso es todo con el registro de usuario y la verificación de correo electrónico. Si tiene algún comentario, pregunta o palabras de aliento, déjelas en el comentario a continuación. Y, por favor, recuerda compartir esta publicación o recomendar este sitio a tus amigos si te resultó útil. Me anima mucho!

¡Que tengas un gran día!