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):
<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:
$("#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:
<?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");
}
}
?>
• 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:
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:
<?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;
}
}
?>
• 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
Generar token único:
$token = bin2hex(random_bytes(50)); - 2 Guardar en BD con expiración: Token + timestamp de 1 hora
- 3 Enviar enlace por email: Usar PHPMailer con enlace seguro
- 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
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:
<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:
Para subir avatares de forma segura:
// 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
// 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
• 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! 👇