Sistema de registro y login de usuarios con PHP y SQL
¿Cuántas veces has necesitado implementar en tu web o aplicación un sistema de registro y login para tus usuarios?. Es una parte básica para un gran porcentaje de webs o aplicaciones que desean tener algún tipo de interacción con sus usuarios o visitantes.
Ya sea para publicar comentarios, añadir algún anuncio o responder consultas, siempre es necesario que el usuario se haya registrado y logeado. De otra manera, estaremos dando acceso libre a cualquiera de los miles de bots de corren por la red esperando a entrar en webs para cometer sus «fechorías».
¿ Qué te puedo ofrecer ?
Encontrarás muchos sistemas de registro de usuarios en la web totalmente funcionales y efectivos, pero también totalmente personalizados según el gusto del creador. Y claro, yo prefiero empezar con el sistema básico y, a partir de aqui, añadirle yo todo lo que necesito.
Pues en este caso, te traigo un sistema de registro y login de usuarios muy simple pero muy eficiente, para implementar en tu web. Este mismo sistema es el que he utilizado como base para varios de mis proyectos. Lógicamente, toda la parte del diseño corre de tu cuenta.
(Al final de este artículo podrás descargar el sistema completo)
Características del sistema:
Registro a través de email y password.
- No recoge otro tipo de datos personales ya que, normalmente, no los necesito. Puedes añadir muy fácilmente nuevos campos en el formulario de registro, si es lo que necesitas. Codificación base64 para el password.
Posibilidad de cambio de contraseña.
- Una vez logeado correctamente, el usuario podrá cambiar su contraseña. Esta opción aparece al inicio de la página, pero puedes colocarlo donde lo necesite. Puedes colocarlo en la cabecera de la web, o en un modal…
Recuerdo de contraseña.
- En el caso de olvido de contraseña, el usuario podrá recibirla añadiendo la direccion de email con la que se registró.
Seguridad
- Este sistema tiene implementado un sistema de seguridad básico, que evita inyectar códigos maliciosos en el momento del registro y valida los campos de los formularios. En cualquier caso, nunca está de más que añadas toda la seguridad que consideres necesaria.
Cookies y Token aleatorio.
En el momento del registro, a la vez que se guarda en la base de datos el email del usuario y su contraseña, codificada en base64 (puedes variarlo si lo necesitas), se crea un token hexadecimal aleatorio de 12 dígitos.
El id del usuario y éste nuevo token, se añaden a unas cookies que quedarán almacenadas en el ordenador de tus usuarios para que, en caso de volver a visitar tu web, no tengan que volver a logearse.
Para añadir un punto más de seguridad y evitar que un usuario intente modificar su cookie de usuario o token aleatorio para acceder como otro usuario, el sistema, cada vez que entra el usuario, hace una consulta a la base de datos y compara si los datos obtenidos de esa consulta corresponden con las cookies internas. En caso contrario, automáticamente elimina las cookies y obliga al usuario a volver a logearse.
Estilos CSS
- A través de archivo form.css, incluido en la carpeta css del sistema, podrás hacer todas las modificaciones que necesites a los estilos de tus formularios. Tienes una infinidad de posibilidades a realizar.
Bootstrap y Fontsawesome
- En este caso, en el interior de la carpeta css, he añadido Bootstrap y Fontsawesome. Verás que desde el <head> de cada formulario se está llamado a estos archivos. Si no los necesitas o deseas añadir otros , puedes ponerlos en la carpeta css o borrarlos.
Estructura del sistema
La estructura es muy simple:
En la carpeta principal añadimos el archivo index.php , que será la web inicial. Si el sistema no encuentra las cookies del usuario logueado, automáticamente se le redirige al archivo de login.
Ahora crearemos unas carpetas css y usuarios, en las que añadiremos los archivos necesarios.
- Carpeta css: añadimos form.css
- Carpeta usuarios: añadimos todos los archivos .php necesarios
Y , a partir de éste punto, vamos con el código:
Empecemos con la base de datos. Hemos de crear la tabla users, con los campos id, username (aquí se guardará el correo electrónico y ha de ser único para evitar dobles registros), password (codificado en md5) , token (generado aleatriamente) , created_at (fecha de registro del usuario).
DROP TABLE IF EXISTS `users`; CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(255) NOT NULL, `token` varchar(255) NOT NULL, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=latin1;
index.php , nuestra página principal de llegada.
<?php require_once "usuarios/config.php"; require_once "usuarios/functions.php"; // Check if the user is logged in, if not then redirect him to login page if(!isset($_COOKIE["userCookie"]) && (!isset($_COOKIE["userToken"]))){ header("location: usuarios/login.php"); exit; echo $_COOKIE["userCookie"];die(); } $user_id = $_COOKIE["userCookie"]; $token = $_COOKIE["userToken"]; //-----si manipulan cookies, las borramos y redireccionamos a login----------- //comprobarToken($user_id, $token); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" href="css/bootstrap431/css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="css/fontawesome/css/all.min.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" type="text/css" href="css/style.css"> </head> <header > <div class="header_left"> <h1><b><?php echo htmlspecialchars($user_id); ?></b></h1> </div> <div class="header_right"> <h1><b><?php echo htmlspecialchars($user_id); ?></b></h1> </div> </header> <body> <p> <a href="usuarios/reset-password.php" class="btn btn-warning">Reset Your Password</a> <a href="usuarios/logout.php" class="btn btn-danger">Sign Out of Your Account</a> </p> </body> </html>
Y ahora, nos vamos a la carpeta usuarios y en su interior añadimos los siguientes archivos:
config.php para la conexión a la base de datos. Para la gestión de usuarios hemos utilizado el método MySQLi asumiendo que trabajas con MySQL. También realizo una conexión PDO para otras partes de la aplicación web.
<?php /* Database credentials. Assuming you are running MySQL server with default setting (user 'root' with no password) */ define('DB_SERVER', 'localhost'); define('DB_USERNAME', 'root'); define('DB_PASSWORD', ''); define('DB_NAME', 'database'); /* Attempt to connect to MySQL database */ $link = mysqli_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME); // Check connection if($link === false){ die("ERROR: Could not connect. " . mysqli_connect_error()); } function conectar() { $server = "localhost"; $user = "root"; $password = ""; $db = "database"; try { $conn = new PDO("mysql:host=$server;dbname=$db", $user, $password, [PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'"]); $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); return $conn; } catch (Exception $ex) { echo $ex->getMessage(); } }
functions.php donde añadiremos los códigos necesarios . En este caso hemos añadido la función que comprueba el token del usuario.
<?php // Include config file require_once "config.php"; //---------Comprobar token usuario---------------------------------- function comprobarToken($user_id, $token_id){ try { $conn = conectar(); $token = $conn->prepare("select id from users where token = '$token_id' "); $token->execute(); $exist = $token->fetch(); if($exist['id']!= $user_id){ //borramos las cookies(); setcookie("userCookie","", time() - 3600, "/"); setcookie("userToken", "", time() - 3600, "/"); // Redirect to login page header("location: usuarios/login.php"); } } catch (Exception $ex) { //echo $ex->getMessage(); } }
register.php donde el usuario se registra con su email y password.
<?php // Include config file require_once "config.php"; // Define variables and initialize with empty values $username = $password = $confirm_password = ""; $username_err = $password_err = $confirm_password_err = ""; // Processing form data when form is submitted if($_SERVER["REQUEST_METHOD"] == "POST"){ // Validate username if(empty(trim($_POST["username"]))){ $username_err = "Please enter a email."; } else{ // Prepare a select statement $sql = "SELECT id FROM users WHERE username = ?"; if($stmt = mysqli_prepare($link, $sql)){ // Bind variables to the prepared statement as parameters mysqli_stmt_bind_param($stmt, "s", $param_username); // Set parameters $param_username = trim($_POST["username"]); // Attempt to execute the prepared statement if(mysqli_stmt_execute($stmt)){ /* store result */ mysqli_stmt_store_result($stmt); if(mysqli_stmt_num_rows($stmt) == 1){ $username_err = "This username is already taken."; } else{ $username = trim($_POST["username"]); } } else{ echo "Oops! Something went wrong. Please try again later."; } } // Close statement mysqli_stmt_close($stmt); } // Validate password if(empty(trim($_POST["password"]))){ $password_err = "Please enter a password."; } elseif(strlen(trim($_POST["password"])) < 6){ $password_err = "Password must have atleast 6 characters."; } else{ $password = trim($_POST["password"]); } // Validate confirm password if(empty(trim($_POST["confirm_password"]))){ $confirm_password_err = "Please confirm password."; } else{ $confirm_password = trim($_POST["confirm_password"]); if(empty($password_err) && ($password != $confirm_password)){ $confirm_password_err = "Password did not match."; } } // Check input errors before inserting in database if(empty($username_err) && empty($password_err) && empty($confirm_password_err)){ // Prepare an insert statement $sql = "INSERT INTO users (username, password, token) VALUES (?, ?, ?)"; if($stmt = mysqli_prepare($link, $sql)){ // Bind variables to the prepared statement as parameters mysqli_stmt_bind_param($stmt, "sss", $param_username, $param_password, $param_token); // Set parameters $param_username = $username; $param_token = bin2hex(openssl_random_pseudo_bytes(6)); //$param_password = password_hash($password, PASSWORD_DEFAULT); // Creates a password hash $param_password = base64_encode($password); // Attempt to execute the prepared statement if(mysqli_stmt_execute($stmt)){ // Redirect to login page header("location: login.php");exit(); } else{ echo "Something went wrong. Please try again later."; } } // Close statement mysqli_stmt_close($stmt); } // Close connection mysqli_close($link); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Sign Up</title> <link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css"> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <link rel="stylesheet" type="text/css" href="../css/form.css"> </head> <body> <div class="wrapper"> <h2>Sign Up</h2> <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post"> <div class="form-group <?php echo (!empty($username_err)) ? 'has-error' : ''; ?>"> <label>Email</label> <input type="email" name="username" class="form-control" value="<?php echo $username; ?>"> <span class="help-block"><?php echo $username_err; ?></span> </div> <div class="form-group <?php echo (!empty($password_err)) ? 'has-error' : ''; ?>"> <label>Password</label> <input type="password" name="password" class="form-control" value="<?php echo $password; ?>"> <span class="help-block"><?php echo $password_err; ?></span> </div> <div class="form-group <?php echo (!empty($confirm_password_err)) ? 'has-error' : ''; ?>"> <label>Confirm Password</label> <input type="password" name="confirm_password" class="form-control" value="<?php echo $confirm_password; ?>"> <span class="help-block"><?php echo $confirm_password_err; ?></span> </div> <div class="form-group"> <input type="submit" class="btn btn-primary" value="Submit"> <input type="reset" class="btn btn-default" value="Reset"> </div> <p>Already have an account? <a href="login.php">Login here</a>.</p> </form> </div> </body> </html>
login.php
<?php // Include config file require_once "config.php"; // Define variables and initialize with empty values $username = $password = ""; $username_err = $password_err = ""; // Processing form data when form is submitted if($_SERVER["REQUEST_METHOD"] == "POST"){ // Check if username is empty if(empty(trim($_POST["username"]))){ $username_err = "Debe escribir su email."; } else{ $username = trim($_POST["username"]); } // Check if password is empty if(empty(trim($_POST["password"]))){ $password_err = "Es necesario un password."; } else{ $password = trim($_POST["password"]); } // Validate credentials if(empty($username_err) && empty($password_err)){ // Prepare a select statement $sql = "SELECT id, username, password,token FROM users WHERE username = ?"; if($stmt = mysqli_prepare($link, $sql)){ // Bind variables to the prepared statement as parameters mysqli_stmt_bind_param($stmt, "s", $param_username); // Set parameters $param_username = $username; // Attempt to execute the prepared statement if(mysqli_stmt_execute($stmt)){ // Store result mysqli_stmt_store_result($stmt); // Check if username exists, if yes then verify password if(mysqli_stmt_num_rows($stmt) == 1){ // Bind result variables mysqli_stmt_bind_result($stmt, $id, $username, $db_password,$usertoken); if(mysqli_stmt_fetch($stmt)){ //echo $id. $username. $db_password; if($password == base64_decode($db_password)){ $caducidad = $year = 60 * 60 * 24 * 365 + time(); setcookie('userCookie', $id, $caducidad,'/' ); setcookie('userToken', $usertoken, $caducidad,'/' ); // Redirect user to welcome page header("location: ../index.php"); }else{ // Display an error message if password is not valid $password_err = "El password no es válido."; } } } else{ // Display an error message if username doesn't exist $username_err = "No existe este usuario."; } } else{ echo "Ha habido un error, inténtelo más tarde."; } } // Close statement mysqli_stmt_close($stmt); } // Close connection mysqli_close($link); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Login</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css"> <link rel="stylesheet" type="text/css" href="../css/form.css"> </head> <body> <div class="wrapper"> <h2>Login</h2> <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post"> <div class="form-group <?php echo (!empty($username_err)) ? 'has-error' : ''; ?>"> <label>Email</label> <input type="email" name="username" class="form-control" value="<?php echo $username; ?>"> <span class="help-block"><?php echo $username_err; ?></span> </div> <div class="form-group <?php echo (!empty($password_err)) ? 'has-error' : ''; ?>"> <label>Password</label> <input type="password" name="password" class="form-control"> <span class="help-block"><?php echo $password_err; ?></span> </div> <div class="form-group"> <input type="submit" class="btn btn-primary" value="Login"> </div> <p>No tienes una cuenta? <a href="register.php">Registrarse</a>.</p> <p>Has olvidado tu password? <a href="recuperar_pass.php">Recuperar password</a>.</p> </form> </div> </body> </html>
reset-password.php
<?php // Include config file require_once "config.php"; // Define variables and initialize with empty values $new_password = $confirm_password = ""; $new_password_err = $confirm_password_err = ""; // Processing form data when form is submitted if($_SERVER["REQUEST_METHOD"] == "POST"){ // Validate new password if(empty(trim($_POST["new_password"]))){ $new_password_err = "Please enter the new password."; } elseif(strlen(trim($_POST["new_password"])) < 6){ $new_password_err = "Password must have atleast 6 characters."; } else{ $new_password = trim($_POST["new_password"]); } // Validate confirm password if(empty(trim($_POST["confirm_password"]))){ $confirm_password_err = "Please confirm the password."; } else{ $confirm_password = trim($_POST["confirm_password"]); if(empty($new_password_err) && ($new_password != $confirm_password)){ $confirm_password_err = "Password did not match."; } } // Check input errors before updating the database if(empty($new_password_err) && empty($confirm_password_err)){ // Prepare an update statement $sql = "UPDATE users SET password = ? WHERE id = ?"; if($stmt = mysqli_prepare($link, $sql)){ // Bind variables to the prepared statement as parameters mysqli_stmt_bind_param($stmt, "si", $param_password, $param_id); // Set parameters $param_password = base64_encode($new_password); $param_id = $_COOKIE["userCookie"]; // Attempt to execute the prepared statement if(mysqli_stmt_execute($stmt)){ header("location: ../index.php"); exit(); } else{ echo "Oops! Something went wrong. Please try again later."; } } // Close statement mysqli_stmt_close($stmt); } // Close connection mysqli_close($link); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Reset Password</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css"> <link rel="stylesheet" type="text/css" href="../css/form.css"> </head> <body> <div class="wrapper"> <h2>Reset Password</h2> <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post"> <div class="form-group <?php echo (!empty($new_password_err)) ? 'has-error' : ''; ?>"> <label>New Password</label> <input type="password" name="new_password" class="form-control" value="<?php echo $new_password; ?>"> <span class="help-block"><?php echo $new_password_err; ?></span> </div> <div class="form-group <?php echo (!empty($confirm_password_err)) ? 'has-error' : ''; ?>"> <label>Confirm Password</label> <input type="password" name="confirm_password" class="form-control"> <span class="help-block"><?php echo $confirm_password_err; ?></span> </div> <div class="form-group"> <input type="submit" class="btn btn-primary" value="Submit"> <a class="btn btn-link" href="welcome.php">Cancel</a> </div> </form> </div> </body> </html>
recuperar_pass.php
<?php // Include config file require_once "config.php"; // Define variables and initialize with empty values $username_err = ""; // Processing form data when form is submitted if($_SERVER["REQUEST_METHOD"] == "POST"){ $stmt = $link->prepare("SELECT * FROM users WHERE username = ?"); $stmt->bind_param("s", $_POST['username']); $stmt->execute(); $result = $stmt->get_result(); // Check if username is empty if($result->num_rows === 0) { $username_err = "Este email no existe."; }else{ $row = $result->fetch_assoc(); $email = $row['username']; //$password_codificado =$row['password'] $password = base64_decode($row['password']); } /* ahora debes generar un email para enviarle al usuario su contraseña y recomendarle que la cambie. Su direccion de email está en la variable '$email' y su contraseña en la variable '$password'*/ $stmt->close(); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Login</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css"> <link rel="stylesheet" type="text/css" href="../css/form.css"> </head> <body> <div class="wrapper"> <h2>Recover password</h2> <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post"> <div class="form-group <?php echo (!empty($username_err)) ? 'has-error' : ''; ?>"> <label>Email</label> <input type="email" name="username" class="form-control"> <span class="help-block"><?php echo $username_err; ?></span> </div> <div class="form-group"> <input type="submit" class="btn btn-primary" value="Recover"> <a type="reset" href="login.php" class="btn btn-default" >Volver</a> </div> </form> </div> </body> </html>
Para generar el email que enviará el password al usuario, visita ESTE POST donde te explico cómo hacerlo.
logout.php
<?php // Initialize the session session_start(); // Unset all of the session variables $_SESSION = array(); // Destroy the session. session_destroy(); //borramos las cookies(); setcookie("userCookie","", time() - 3600, "/"); setcookie("userToken", "", time() - 3600, "/"); // Redirect to login page header("location: login.php"); exit; ?>
Y hasta aqui, el Sistema de Usuarios para tus webs o palicaciones. Verás que es muy simple y aún tiene mucho margen de mejora, pero considero que es mejor que le añadas tú lo que necesites en lugar de usar un Sistema excesivamente grande o con características inútiles para ti.
Por ejemplo puedes añadirle JavaScript para hacer validaciones, o añadir un loader, o más campos en tus formularios, o una elección de avatar….ya lo ves, lo que necesites.
AQUI puedes descargarte el sistema completo. Sólo has de adaptarlo a tu base de datos y empezar a modificarlo a tu gusto.
Espero que te sea de utilidad. Hasta el próximo post!!