Cookies en PHP: setcookie(), lectura y atributos de seguridad httponly y secure

Las cookies son pequeños fragmentos de texto que el servidor envía al navegador y que este devuelve en cada petición posterior. En PHP se crean con setcookie(), se leen con $_COOKIE y se eliminan enviando una cookie con fecha de expiración en el pasado. Configurar correctamente sus atributos de seguridad es fundamental para proteger a los usuarios.

setcookie(): crear una cookie

setcookie() debe llamarse antes de cualquier salida HTML. Sus parámetros principales son el nombre, el valor, la expiración y la ruta. A partir de PHP 7.3 también acepta un array de opciones que incluye los atributos de seguridad modernos.

<?php
// Cookie básica que expira en 30 días
setcookie('idioma', 'es', time() + 30 * 24 * 3600, '/');

// Forma moderna con array de opciones (PHP 7.3+)
setcookie('preferencia', 'oscuro', [
    'expires'  => time() + 7 * 24 * 3600,
    'path'     => '/',
    'domain'   => 'programacion.net',
    'secure'   => true,       // solo HTTPS
    'httponly' => true,        // no accesible desde JS
    'samesite' => 'Lax',      // protección CSRF básica
]);
?>

Leer cookies con $_COOKIE

La superglobal $_COOKIE contiene las cookies que el navegador ha enviado en la petición actual. Las cookies recién creadas con setcookie() no aparecen en $_COOKIE hasta la siguiente petición.

<?php
// Leer una cookie con valor por defecto
$idioma = $_COOKIE['idioma'] ?? 'es';

// Nunca usar el valor de una cookie directamente en HTML
$preferencia = htmlspecialchars($_COOKIE['preferencia'] ?? '', ENT_QUOTES, 'UTF-8');
echo "Tema seleccionado: $preferencia";

// Comprobar si existe una cookie concreta
if (isset($_COOKIE['recordar_usuario'])) {
    $tokenRecordado = $_COOKIE['recordar_usuario'];
    // Verificar el token en la BD antes de autenticar
}
?>

Cookies de sesión vs. cookies persistentes

Una cookie de sesión no tiene fecha de expiración y el navegador la elimina al cerrarse. Una cookie persistente tiene un expires en el futuro y sobrevive al cierre del navegador. Usar una u otra depende de si se quiere que los datos persistan entre sesiones.

<?php
// Cookie de sesión (sin expires o expires=0): desaparece al cerrar el navegador
setcookie('carrito_id', 'abc123', [
    'path'     => '/',
    'httponly' => true,
    'samesite' => 'Lax',
]);

// Cookie persistente: dura 90 días
setcookie('usuario_recordado', $token, [
    'expires'  => time() + 90 * 24 * 3600,
    'path'     => '/',
    'secure'   => true,
    'httponly' => true,
    'samesite' => 'Strict',
]);
?>

Eliminar una cookie

Para borrar una cookie hay que enviar al navegador una nueva cookie con el mismo nombre, misma ruta y misma sesión, pero con una fecha de expiración en el pasado.

<?php
function eliminarCookie(string $nombre, string $ruta = '/'): void
{
    setcookie($nombre, '', [
        'expires'  => time() - 3600, // en el pasado
        'path'     => $ruta,
        'secure'   => true,
        'httponly' => true,
        'samesite' => 'Lax',
    ]);
    unset($_COOKIE[$nombre]); // limpiar también en la petición actual
}

eliminarCookie('preferencia');
eliminarCookie('recordar_usuario');
?>

Firmar cookies con hash_hmac()

Para detectar si el usuario ha manipulado el valor de una cookie, se puede firmarla con hash_hmac() usando una clave secreta. Al leerla, se verifica la firma antes de confiar en el valor.

<?php
const COOKIE_SECRET = 'clave_super_secreta_de_al_menos_32_caracteres';

function crearCookieFirmada(string $nombre, string $valor, int $expira): void
{
    $firma       = hash_hmac('sha256', $valor, COOKIE_SECRET);
    $valorFirmado = base64_encode($valor . '|' . $firma);

    setcookie($nombre, $valorFirmado, [
        'expires'  => $expira,
        'path'     => '/',
        'secure'   => true,
        'httponly' => true,
        'samesite' => 'Lax',
    ]);
}

function leerCookieFirmada(string $nombre): ?string
{
    if (!isset($_COOKIE[$nombre])) {
        return null;
    }

    $decodificado = base64_decode($_COOKIE[$nombre]);
    $partes       = explode('|', $decodificado, 2);

    if (count($partes) !== 2) {
        return null;
    }

    [$valor, $firmaRecibida] = $partes;
    $firmaEsperada = hash_hmac('sha256', $valor, COOKIE_SECRET);

    // Comparación segura frente a timing attacks
    if (!hash_equals($firmaEsperada, $firmaRecibida)) {
        return null; // cookie manipulada
    }

    return $valor;
}

// Crear cookie firmada que dura 7 días
crearCookieFirmada('nivel', 'premium', time() + 7 * 24 * 3600);

// Leer y verificar
$nivel = leerCookieFirmada('nivel'); // 'premium' o null si hay manipulación
?>

La documentación oficial de setcookie() detalla todos los parámetros disponibles, las diferencias entre navegadores para el atributo SameSite y las consideraciones sobre codificación del valor de la cookie.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP