🔐 Módulo 8 - Proyecto Final

Sistema de login con PHP y MySQL: Autenticación completa

¿Quieres integrar todo lo aprendido en un proyecto real? En esta guía aprenderás a crear un sistema de autenticación completo: registro, login, recuperación de contraseña y dashboard con AdminLTE.

⏱️ ~16 min de lectura 🎯 Nivel: Intermedio-Avanzado 🔐 Proyecto listo para producción

Integrar registro, login, recuperación de contraseña y dashboard en un solo sistema es el paso definitivo para convertirte en desarrollador full stack. En esta guía construirás un sistema de autenticación profesional con seguridad avanzada.

Sistema de Autenticación: Registro y Validaciones

Primero, crea el formulario de registro (registro.php):

📝 Ejemplo: Formulario de registro
<form id="formRegistro" method="POST" action="procesar_registro.php">
  <div class="form-group">
    <label for="nombre">Nombre completo</label>
    <input type="text" name="nombre" id="nombre" required 
           class="form-control" placeholder="Tu nombre">
  </div>
  <div class="form-group">
    <label for="email">Correo electrónico</label>
    <input type="email" name="email" id="email" required 
           class="form-control" placeholder="tu@email.com">
  </div>
  <div class="form-group">
    <label for="password">Contraseña</label>
    <input type="password" name="password" id="password" required 
           class="form-control" minlength="8" placeholder="Mínimo 8 caracteres">
  </div>
  <button type="submit" class="btn btn-primary">Registrar</button>
</form>

Validaciones en el Front-end con jQuery y SweetAlert

Usa jQuery para validar antes de enviar al servidor:

⚡ Validación con jQuery + SweetAlert
$("#formRegistro").on("submit", function(e) {
    let password = $("input[name='password']").val();
    let email = $("input[name='email']").val();
    
    // Validar longitud de contraseña
    if (password.length < 8) {
        swal("Error", "La contraseña debe tener al menos 8 caracteres", "error");
        e.preventDefault();
        return false;
    }
    
    // Validar formato de email
    let emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(email)) {
        swal("Error", "Ingresa un correo electrónico válido", "error");
        e.preventDefault();
        return false;
    }
    
    // Mostrar loading
    $(this).find('button[type="submit"]').prop('disabled', true).text('Registrando...');
});

Nota: La validación en frontend es para experiencia de usuario. Siempre valida también en el backend para seguridad.

Procesamiento del Registro en el Servidor con PHP y MySQLi

En procesar_registro.php, guarda los datos de forma segura:

🔐 Ejemplo: Registrar usuario con seguridad
<?php
// procesar_registro.php
session_start();
require_once 'config.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Sanitizar y validar entrada
    $nombre = htmlspecialchars(trim($_POST['nombre']));
    $email = filter_var(trim($_POST['email']), FILTER_SANITIZE_EMAIL);
    $password = $_POST['password'];
    
    // Validaciones backend
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $_SESSION['error'] = "Email inválido";
        header("Location: registro.php");
        exit;
    }
    
    if (strlen($password) < 8) {
        $_SESSION['error'] = "La contraseña debe tener al menos 8 caracteres";
        header("Location: registro.php");
        exit;
    }
    
    // Verificar si el email ya existe
    $stmt = $conn->prepare("SELECT id FROM usuarios WHERE email = ?");
    $stmt->bind_param("s", $email);
    $stmt->execute();
    
    if ($stmt->get_result()->num_rows > 0) {
        $_SESSION['error'] = "Este email ya está registrado";
        header("Location: registro.php");
        exit;
    }
    
    // Hash de contraseña seguro
    $password_hash = password_hash($password, PASSWORD_DEFAULT);
    
    // Insertar usuario con prepared statement
    $stmt = $conn->prepare("INSERT INTO usuarios (nombre, email, password) VALUES (?, ?, ?)");
    $stmt->bind_param("sss", $nombre, $email, $password_hash);
    
    if ($stmt->execute()) {
        $_SESSION['success'] = "Registro exitoso. ¡Bienvenido!";
        $_SESSION['user_id'] = $conn->insert_id;
        $_SESSION['nombre'] = $nombre;
        header("Location: dashboard.php");
    } else {
        error_log("Error al registrar: " . $stmt->error);
        $_SESSION['error'] = "Error al registrar. Intenta más tarde.";
        header("Location: registro.php");
    }
}
?>
✅ Buenas prácticas aplicadas

password_hash() para contraseñas seguras
• Prepared statements para prevenir SQL injection
htmlspecialchars() para prevenir XSS
• Validación en frontend + backend (defensa en profundidad)

Conexión a la Base de Datos: Estructura de la tabla usuarios

Asegúrate de tener una tabla usuarios con los campos necesarios:

🗄️ Ejemplo: Crear tabla usuarios
CREATE TABLE usuarios (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    token_recuperacion VARCHAR(255) NULL,
    token_expira DATETIME NULL,
    rol ENUM('user', 'admin') DEFAULT 'user',
    avatar VARCHAR(255) DEFAULT 'default.png',
    creado TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    actualizado TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_email (email),
    INDEX idx_token (token_recuperacion)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Implementación del Login y Autenticación de Usuarios

En login.php, verifica credenciales de forma segura:

🔑 Ejemplo: Login seguro con password_verify
<?php
// login.php - Procesar login
session_start();
require_once 'config.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $email = filter_var(trim($_POST['email']), FILTER_SANITIZE_EMAIL);
    $password = $_POST['password'];
    
    // Buscar usuario por email
    $stmt = $conn->prepare("SELECT id, nombre, email, password, rol FROM usuarios WHERE email = ?");
    $stmt->bind_param("s", $email);
    $stmt->execute();
    $result = $stmt->get_result();
    $user = $result->fetch_assoc();
    
    // Verificar contraseña con password_verify
    if ($user && password_verify($password, $user['password'])) {
        // Regenerar ID de sesión para prevenir fijación
        session_regenerate_id(true);
        
        // Guardar datos en sesión
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['nombre'] = $user['nombre'];
        $_SESSION['email'] = $user['email'];
        $_SESSION['rol'] = $user['rol'];
        $_SESSION['last_activity'] = time();
        
        // Redirigir al dashboard
        header("Location: dashboard.php");
        exit;
    } else {
        // Mensaje genérico para no revelar si el email existe
        $_SESSION['error'] = "Credenciales incorrectas";
        header("Location: login.php");
        exit;
    }
}
?>
⚠️ Seguridad en login

• Usa siempre el mismo mensaje de error para email/password incorrectos
• Regenera el ID de sesión después del login exitoso
• Implementa rate limiting para prevenir ataques de fuerza bruta
• Usa HTTPS para proteger las credenciales en tránsito

Recuperación de Contraseña con PHPMailer

Cuando el usuario olvida su contraseña, sigue este flujo seguro:

  1. 1 Generar token único: $token = bin2hex(random_bytes(50));
  2. 2 Guardar en BD con expiración: Token + timestamp de 1 hora
  3. 3 Enviar enlace por email: Usar PHPMailer con enlace seguro
  4. 4 Validar y permitir cambio: Verificar token, expiración y usado

Dashboard con AdminLTE

AdminLTE es un tema administrativo gratuito basado en Bootstrap, ideal para dashboards profesionales:

  • Descarga AdminLTE: adminlte.io
  • Incluye CSS y JS en tu dashboard.php
  • Usa sus componentes: navbar, sidebar, cards, tablas DataTables
  • Personaliza colores y branding para tu proyecto
💡 Tip de implementación

Protege tu dashboard verificando la sesión al inicio de cada página: if (!isset($_SESSION['user_id'])) { header("Location: login.php"); exit; }

Perfil de Usuario con Edición de Datos

Permite al usuario editar su información personal de forma segura:

✏️ Ejemplo: Formulario de edición de perfil
<form method="POST" action="actualizar_perfil.php" enctype="multipart/form-data">
  <div class="form-group">
    <label>Nombre</label>
    <input type="text" name="nombre" value="<?php echo htmlspecialchars($user['nombre']); ?>" 
           class="form-control" required>
  </div>
  <div class="form-group">
    <label>Email</label>
    <input type="email" name="email" value="<?php echo htmlspecialchars($user['email']); ?>" 
           class="form-control" required>
  </div>
  <div class="form-group">
    <label>Avatar</label>
    <input type="file" name="avatar" class="form-control" accept="image/*">
    <small class="text-muted">PNG, JPG. Máx. 2MB</small>
  </div>
  <button type="submit" class="btn btn-primary">Guardar cambios</button>
</form>

CRUD de Usuarios con Subida de Avatares

Desde el dashboard (solo para rol admin), puedes gestionar usuarios:

Create Registrar nuevos usuarios
👁️ Read Listar usuarios en tabla
✏️ Update Editar datos y avatar
🗑️ Delete Eliminar con confirmación

Para subir avatares de forma segura:

🖼️ Ejemplo: Subida segura de avatar
// Validar y procesar avatar
if (isset($_FILES['avatar']) && $_FILES['avatar']['error'] === 0) {
    $allowed = ['jpg', 'jpeg', 'png', 'gif'];
    $filename = $_FILES['avatar']['name'];
    $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    
    // Validar extensión
    if (!in_array($ext, $allowed)) {
        $_SESSION['error'] = "Formato de imagen no permitido";
        header("Location: perfil.php");
        exit;
    }
    
    // Validar tamaño (máx 2MB)
    if ($_FILES['avatar']['size'] > 2 * 1024 * 1024) {
        $_SESSION['error'] = "La imagen es demasiado grande (máx 2MB)";
        header("Location: perfil.php");
        exit;
    }
    
    // Generar nombre único para prevenir colisiones
    $new_filename = uniqid('avatar_', true) . '.' . $ext;
    $target_path = 'uploads/avatars/' . $new_filename;
    
    // Mover archivo
    if (move_uploaded_file($_FILES['avatar']['tmp_name'], $target_path)) {
        // Actualizar BD con nuevo avatar
        $stmt = $conn->prepare("UPDATE usuarios SET avatar = ? WHERE id = ?");
        $stmt->bind_param("si", $new_filename, $_SESSION['user_id']);
        $stmt->execute();
    }
}

Implementación de AJAX para una Mejor Experiencia

Elimina recargas de página usando AJAX para acciones comunes:

  • Guardar perfil sin recargar la página
  • Eliminar usuario con SweetAlert + AJAX + confirmación
  • Cargar listado de usuarios dinámicamente con paginación
  • Validar email disponible en tiempo real
⚡ Ejemplo: Eliminar usuario con AJAX
// Frontend: jQuery + SweetAlert
$('.btn-eliminar').on('click', function() {
    let userId = $(this).data('id');
    
    swal({
        title: "¿Estás seguro?",
        text: "Esta acción no se puede deshacer",
        icon: "warning",
        buttons: ["Cancelar", "Eliminar"],
        dangerMode: true
    }).then((confirm) => {
        if (confirm) {
            $.ajax({
                url: 'eliminar_usuario.php',
                type: 'POST',
                data: { id: userId },
                success: function(response) {
                    if (response.success) {
                        swal("Eliminado", response.message, "success")
                            .then(() => location.reload());
                    } else {
                        swal("Error", response.message, "error");
                    }
                },
                error: function() {
                    swal("Error", "No se pudo procesar la solicitud", "error");
                }
            });
        }
    });
});

Seguridad Avanzada: Checklist de implementación

🔐 Medidas de seguridad implementadas

  • Validación de roles: Solo admins pueden gestionar usuarios (if ($_SESSION['rol'] !== 'admin'))
  • Protección CSRF: Tokens ocultos en formularios para prevenir ataques cross-site
  • Redirección segura: Validar URLs de redirección para prevenir open redirect
  • Manejo de sesiones: session_regenerate_id() tras login, timeout de inactividad
  • Headers de seguridad: Content-Security-Policy, X-Frame-Options, X-XSS-Protection
  • Logging de auditoría: Registrar intentos de login fallidos y acciones sensibles
🚨 Errores comunes a evitar

• Nunca mostrar errores detallados al usuario en producción
• No confiar en validación solo del frontend
• No almacenar contraseñas en texto plano (siempre usar hash)
• No olvidar cerrar sesiones al hacer logout (session_destroy())

¿Qué sigue después de este sistema de login?

¡Felicidades! Has completado el módulo 8 y construido un sistema de autenticación profesional. Este es el proyecto que puedes mostrar en tu portafolio para conseguir trabajo como desarrollador.

En el siguiente y último módulo, aprenderás a desplegar tu aplicación en producción con dominio real, hosting y SSL, para que tu proyecto esté disponible para todo el mundo.

📚 ¿Quieres la versión completa del libro?

Esta guía es una versión resumida del libro "Programación Full-Stack", que incluye el código completo del sistema de login, ejercicios resueltos y explicaciones profundas de seguridad.

Comprar en Amazon →

¿Tienes dudas sobre autenticación, seguridad o AdminLTE? ¡Déjalas en los comentarios! 👇