Comprender el funcionamiento y uso de las cookies en aplicaciones web

Las cookies surgieron como solución al problema de la falta de estado en HTTP. Al ser un protocolo sin estado, el servidor necesita una forma de recordar información del cliente entre solicitudes. Las cookies permiten almacenar datos del usuario —como preferencias, sesiones o autenticación— directamente en el navegador, facilitando su recuperación en futuras interacciones.

El estándar actual que rige su comportamiento es RFC 6265, un acuerdo entre navegadores y servidores para gestionar estos pequeños fragmentos de datos de forma consistente y segura.

Flujo básico de operación

El ciclo de vida de una cookie sigue este patrón:

  • El servidor envía una cookie mediante la cabecera Set-Cookie.
  • El navegador la almacena localmente si cumple con las políticas de dominio y seguridad.
  • En futuras peticiones al mismo dominio y ruta, el navegador incluye la cookie en la cabecera Cookie.

Existen restricciones importantes: límite de 4KB por cookie, máximo de 20 cookies por dominio y 300 en total por navegador. Además, están sujetas a políticas de mismo origen (same-origin policy) y pueden estar restringidas por atributos como HttpOnly o Secure.

Configuración desde el servidor

La cabecera Set-Cookie define los parámetros de la cookie. Un ejemplo típico:

Set-Cookie: sessionId=abc123; Domain=.ejemplo.com; Path=/; Max-Age=3600; HttpOnly; Secure

Componentes clave:

  • sessionId=abc123: Par clave-valor obligatorio.
  • Domain: Define qué sbudominios pueden acceder a la cookie.
  • Path: Restringe la cookie a ciertas rutas del sitio.
  • Max-Age: Tiempo de vida en segundos (prioritario sobre Expires).
  • HttpOnly: Impide acceso desde JavaScript.
  • Secure: Solo se envía sobre conexiones HTTPS.

Ejemplo en Node.js usando una función auxiliar para generar cookies:

function crearCookie(clave, valor, config = {}) {
    const codificar = encodeURIComponent;
    const partes = [`${codificar(clave)}=${codificar(valor || "")}`];

    if (config.dominio) partes.push(`Domain=${config.dominio}`);
    if (config.ruta) partes.push(`Path=${config.ruta}`);
    if (config.expira) partes.push(`Expires=${config.expira.toUTCString()}`);
    if (config.duracion) partes.push(`Max-Age=${config.duracion}`);
    if (config.httpOnly) partes.push("HttpOnly");
    if (config.seguro) partes.push("Secure");

    return partes.join("; ");
}

// Uso
res.setHeader("Set-Cookie", crearCookie("visitante", "true", {
    dominio: ".midominio.com",
    duracion: 1000,
    httpOnly: true
}));

Lectura en el servidor

Al recibir múltiples cookies con el mismo nombre (por distintas rutas o dominios), el servidor debe priorizar la más específica según la URL solicitada. Una función básica para parsear:

function leerCookies(cadena) {
    if (!cadena) return {};
    
    const decodificar = decodeURIComponent;
    const resultado = {};
    const items = cadena.split(/\s*;\s*/);

    for (let item of items) {
        const [claveCodificada, ...resto] = item.split("=");
        const clave = decodificar(claveCodificada);
        const valor = resto.length ? decodificar(resto.join("=")) : null;

        if (!(clave in resultado)) {
            resultado[clave] = valor;
        }
    }

    return resultado;
}

Manipulación desde el cliente

JavaScript accede a las cookies mediante document.cookie, pero su interfaz es limitada: leer devuelve todas las cookies accesibles como cadena, y escribir añade o actualiza una cookie sin sobrescribir las demás.

Para simplificar su uso, se puede encapsular en un objeto utilitario:

const gestorCookies = {
    obtener(nombre) {
        const prefijo = `${encodeURIComponent(nombre)}=`;
        const inicio = document.cookie.indexOf(prefijo);
        
        if (inicio === -1) return null;

        const fin = document.cookie.indexOf(";", inicio);
        const valorCodificado = fin === -1 
            ? document.cookie.slice(inicio + prefijo.length)
            : document.cookie.slice(inicio + prefijo.length, fin);

        return decodeURIComponent(valorCodificado);
    },

    establecer(nombre, valor, opciones = {}) {
        const codificar = encodeURIComponent;
        const partes = [`${codificar(nombre)}=${codificar(valor ?? "")}`];

        if (opciones.dominio) partes.push(`Domain=${opciones.dominio}`);
        if (opciones.ruta) partes.push(`Path=${opciones.ruta}`);
        if (opciones.expira) partes.push(`Expires=${opciones.expira.toUTCString()}`);
        if (opciones.duracion) partes.push(`Max-Age=${opciones.duracion}`);
        if (opciones.httpOnly) partes.push("HttpOnly");
        if (opciones.seguro) partes.push("Secure");

        document.cookie = partes.join("; ");
    },

    eliminar(nombre, opciones = {}) {
        this.establecer(nombre, "", { ...opciones, expira: new Date(0) });
    }
};

Protección con HttpOnly

El atributo HttpOnly impide que scripts del lado del cliente accedan a la cookie mediante document.cookie. Esto mitiga ataques de tipo XSS (Cross-Site Scripting), ya que aunque un atacante inyecte código malicioso, no podrá robar cookies sensibles como tokens de sesión. Solo el servidor puede modificarlas o leerlas durante las peticiones HTTP.

Etiquetas: Cookies HTTP RFC6265 JavaScript Node.js

Publicado el 10-4 00:13