PHP

Guía de Autenticación Segura en PHP: Control de Usuarios con Sesiones y Cookies

15 agosto, 2026
8 lecturas
Guía de Autenticación Segura en PHP: Sesiones, Cookies y Hash

La seguridad en la gestión de usuarios es el pilar de cualquier aplicación web moderna. Implementar una autenticación segura en PHP requiere combinar el almacenamiento de contraseñas con password_hash(), la gestión de estado mediante $_SESSION y la configuración estricta de cookies para evitar accesos no autorizados.

Hasheado de Contraseñas con password_hash()

Hasheado de Contraseñas con password_hash()

Hasheado de Contraseñas con password_hash()

Almacenar contraseñas en texto plano o usar algoritmos obsoletos como MD5 o SHA1 expone la base de datos a filtraciones. PHP ofrece las funciones nativas password_hash() y password_verify(), las cuales generan un salt aleatorio automático y emplean algoritmos robustos como Bcrypt y Argon2id.

Registro y Verificación de Credenciales

  • Procesamiento de contraseña en el registro:

    PHP

     

    $password = $_POST['password'];
    $hashBD = password_hash($password, PASSWORD_DEFAULT);
    
  • Validación durante el inicio de sesión:

    PHP

     

    if (password_verify($passwordIngresada, $hashBD)) {
        // Credenciales válidas: Iniciar sesión
    }

Control de Estado y Seguridad con $_SESSION

Las variables de sesión permiten mantener la identidad del usuario a través de múltiples peticiones HTTP. Sin embargo, para prevenir ataques de fijación de sesión (Session Fixation) y secuestro de sesión (Session Hijacking), es indispensable configurar los parámetros de las cookies de sesión antes de iniciarlas.

Configuración Recomendada del Parámetro de Sesión

PHP

 

session_set_cookie_params([
    'lifetime' => 0,        // Expira al cerrar el navegador
    'path' => '/',
    'secure' => true,       // Solo transmite cookies sobre HTTPS
    'httponly' => true,     // Impide el acceso a las cookies vía JavaScript (mitiga XSS)
    'samesite' => 'Lax'     // Previene ataques de falsificación de petición (CSRF)
]);

session_start();
session_regenerate_id(true); // Renueva la ID de sesión al autenticar

Persistencia de Usuarios con Cookies ("Recordarme")

Persistencia de Usuarios con Cookies ("Recordarme")

Persistencia de Usuarios con Cookies ("Recordarme")

Guardar contraseñas o el ID directo del usuario en una cookie es un fallo grave de seguridad. El estándar de la industria exige el patrón de tokens selector/validador de un solo uso.

PHP

 

// Generar un par de tokens criptográficamente seguros
$selector = bin2hex(random_bytes(8));
$validator = random_bytes(32);

// Crear cookie persistente por 30 días
$tokenValue = $selector . ':' . bin2hex($validator);
$hashedValidator = hash('sha256', $validator);

setcookie(
    'remember_me',
    $tokenValue,
    [
        'expires' => time() + (86400 * 30),
        'path' => '/',
        'secure' => true,
        'httponly' => true,
        'samesite' => 'Lax'
    ]
);

// Guardar $selector y $hashedValidator en la base de datos asociados al usuario

Comparativa de Mecanismos de Seguridad

Mecanismo Función Principal Riesgo de Seguridad que Previene
password_hash() Cifrado unidireccional de clave Filtración de credenciales en fuga de datos
session_regenerate_id() Renovación del identificador de sesión Fijación de sesión (Session Fixation)
Cookie HttpOnly + Secure Restricción de acceso en el cliente Robo de sesión mediante XSS e intercepción en red
Flag SameSite=Lax Aislamiento de origen de cookies Falsificación de Peticiones en Sitios Cruzados (CSRF)

Control de Acceso en Páginas Protegidas

Para restringir secciones privadas de la aplicación, incluye un script de verificación en el encabezado de cada archivo protegido:

PHP

 

session_start();

// Verificar si el usuario está autenticado
if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
    header('Location: /login.php');
    exit;
}

// Control de expiración por inactividad (30 minutos)
if (time() - $_SESSION['last_activity'] > 1800) {
    session_unset();
    session_destroy();
    header('Location: /login.php?error=timeout');
    exit;
}

$_SESSION['last_activity'] = time(); // Actualizar tiempo de última interacción

Buenas Prácticas para Proteger tu Sistema de Autenticación

  • Sanitiza las entradas: Valida y limpia siempre los datos provenientes de formularios con filter_input().
  • Cierra sesiones inactivas: Implementa un tiempo de expiración automático tras 15 o 30 minutos de inactividad.
  • Aplica tokens Anti-CSRF: Incluye un token único e impredecible en cada formulario POST para confirmar el origen de la petición.
  • Destrucción completa al cerrar sesión: Al hacer logout, vacía $_SESSION, elimina la cookie de sesión del navegador y destruye la sesión en el servidor con session_destroy().

Código completo archivos login.php y logout.php utilizando PDO y MySQL

Código completo archivos login.php y logout.php utilizando PDO y MySQL

Código completo archivos login.php y logout.php utilizando PDO y MySQL

config.php (Conexión PDO Segura)

PHP

 

<?php
declare(strict_types=1);

$host    = '127.0.0.1';
$db      = 'mi_base_de_datos';
$user    = 'usuario_db';
$pass    = 'contrasena_segura';
$charset = 'utf8mb4';

$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
];

try {
    $pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
    // Registrar error en servidor y ocultar detalles sensibles al cliente
    error_log('Error de conexión a la base de datos: ' . $e->getMessage());
    exit('Error interno en el servidor.');
}

login.php (Procesamiento y Formulario)

PHP

 

<?php
declare(strict_types=1);

// Encabezados de seguridad HTTP
header("X-Frame-Options: DENY");
header("X-Content-Type-Options: nosniff");
header("Referrer-Policy: strict-origin-when-cross-origin");

// Configurar parámetros de cookie antes de iniciar la sesión
session_set_cookie_params([
    'lifetime' => 0,
    'path'     => '/',
    'domain'   => '',
    'secure'   => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
    'httponly' => true,
    'samesite' => 'Lax'
]);

session_start();

// Redirigir al usuario si ya tiene una sesión activa
if (isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true) {
    header('Location: dashboard.php');
    exit;
}

require_once 'config.php';

// Generar token Anti-CSRF
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

$error = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 1. Validar Token Anti-CSRF
    $tokenSubmitted = $_POST['csrf_token'] ?? '';
    if (!hash_equals($_SESSION['csrf_token'], $tokenSubmitted)) {
        http_response_code(403);
        die('Solicitud no válida (CSRF detectado).');
    }

    // 2. Validar y sanitizar datos recibidos
    $email    = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
    $password = $_POST['password'] ?? '';

    if (!$email || empty($password)) {
        $error = 'Por favor, ingresa un correo y contraseña válidos.';
    } else {
        // 3. Consulta preparada PDO contra inyección SQL
        $stmt = $pdo->prepare('SELECT id, password_hash FROM users WHERE email = :email LIMIT 1');
        $stmt->execute(['email' => $email]);
        $user = $stmt->fetch();

        // 4. Verificar la contraseña con el hash almacenado
        if ($user && password_verify($password, $user['password_hash'])) {
            // Renovar ID de sesión para prevenir Session Fixation
            session_regenerate_id(true);

            $_SESSION['user_id']       = $user['id'];
            $_SESSION['logged_in']     = true;
            $_SESSION['last_activity'] = time();

            // Actualizar hash si la estrategia o costo del algoritmo ha cambiado
            if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
                $newHash = password_hash($password, PASSWORD_DEFAULT);
                $updateStmt = $pdo->prepare('UPDATE users SET password_hash = :hash WHERE id = :id');
                $updateStmt->execute(['hash' => $newHash, 'id' => $user['id']]);
            }

            unset($_SESSION['csrf_token']);
            header('Location: dashboard.php');
            exit;
        } else {
            // Mensaje genérico para mitigar ataques de enumeración
            $error = 'Credenciales incorrectas.';
        }
    }
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Iniciar Sesión</title>
</head>
<body>
    <h2>Iniciar Sesión</h2>

    <?php if (!empty($error)): ?>
        <p style="color: red;"><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></p>
    <?php endif; ?>

    <form action="login.php" method="POST">
        <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES, 'UTF-8') ?>">

        <div>
            <label for="email">Correo electrónico:</label><br>
            <input type="email" id="email" name="email" required autocomplete="email">
        </div>
        <br>
        <div>
            <label for="password">Contraseña:</label><br>
            <input type="password" id="password" name="password" required autocomplete="current-password">
        </div>
        <br>
        <button type="submit">Ingresar</button>
    </form>
</body>
</html>

logout.php (Cierre Seguro)

PHP

 

<?php
declare(strict_types=1);

session_start();

// 1. Vaciar el arreglo de sesión
$_SESSION = [];

// 2. Eliminar la cookie de sesión en el cliente
if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(
        session_name(),
        '',
        time() - 42000,
        $params["path"],
        $params["domain"],
        $params["secure"],
        $params["httponly"]
    );
}

// 3. Destruir la sesión en el servidor
session_destroy();

// 4. Redirigir al inicio de sesión
header('Location: login.php');
exit;

Tabla MySQL (schema.sql)

SQL

 

CREATE TABLE IF NOT EXISTS `users` (
  `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `email` VARCHAR(255) NOT NULL UNIQUE,
  `password_hash` VARCHAR(255) NOT NULL,
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Tutoriales Recomendados

Sigue explorando publicaciones de la misma categoría

Ver todos