¿Qué son las cookies — Gestión del estado HTTP
Al finalizar este tema
Comprenderá por qué HTTP es un protocolo "sin estado", podrá explicar cómo las cookies resuelven este problema y aprenderá a configurar y leer cookies en Express.
HTTP no tiene memoria
El protocolo HTTP es sin estado. Una vez que el servidor procesa una solicitud, olvida por completo al cliente.
Client: GET /page1 → Server: "Contenido de la página 1"
Client: GET /page2 → Server: "¿Quién eres? Encantado. Contenido de la página 2"El servidor no puede saber si el usuario que solicitó /page1 hace un segundo es el mismo. Funciones como el inicio de sesión, el carrito de compras y la configuración del modo oscuro —cualquier funcionalidad que requiera “recordar” solicitudes anteriores— son imposibles debido a esta característica de falta de estado.
Las cookies solucionan este problema. El servidor le indica al cliente: “Guarda esta información y muéstrala la próxima vez”.
Cómo funcionan las cookies
Las cookies se intercambian a través de encabezados HTTP.
1. Servidor → Cliente (encabezado de respuesta)
Set-Cookie: username=Hoon; Path=/
2. Cliente → Servidor (encabezado de solicitud de la siguiente solicitud)
Cookie: username=HoonUna vez configurado, el navegador incluye automáticamente las cookies en todas las solicitudes al mismo dominio. El navegador las envía automáticamente, sin necesidad de que el servidor lo solicite. Este es el mecanismo fundamental de las cookies.
Uso de cookies en Express
Configuración de cookies
const express = require('express');
const app = express();
app.get('/login', (req, res) => {
// Se envía el encabezado Set-Cookie
res.cookie('username', 'Hoon', {
maxAge: 24 * 60 * 60 * 1000, // 1 day (milliseconds)
httpOnly: true,
secure: false, // Solo se transmite en HTTPS (false durante el desarrollo)
sameSite: 'lax'
});
res.json({ message: 'Logged in, cookie set' });
});Leer cookies
Para leer las cookies, se necesita el middleware cookie-parser.
npm install cookie-parserconst cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get('/profile', (req, res) => {
const username = req.cookies.username;
if (!username) {
return res.status(401).json({ error: 'Not logged in' });
}
res.json({ message: `Welcome back, ${username}` });
});cookie-parser analiza el encabezado Cookie y lo convierte en un objeto req.cookies. Sin este componente intermedio, sería necesario analizar los encabezados directamente.
Eliminación de cookies
app.get('/logout', (req, res) => {
res.clearCookie('username');
res.json({ message: 'Logged out, cookie cleared' });
});clearCookie elimina inmediatamente la cookie con el mismo nombre.
Propiedades de las cookies
| Propiedad | Descripción | Ejemplo |
|---|---|---|
maxAge | Tiempo de vida (en milisegundos). Se elimina automáticamente al transcurrir este tiempo | 86400000 (1 día) |
expires | Fecha de caducidad (objeto Date). Se usa con menos frecuencia que maxAge | new Date('2026-12-31') |
httpOnly | No accesible desde JavaScript (elemento clave para la protección contra XSS) | true |
secure | Se envía solo a través de conexiones HTTPS | true |
sameSite | Indica si la cookie se incluye en las solicitudes de otros sitios | 'strict', 'lax', 'none' |
path | Envía la cookie solo dentro de esta ruta | '/' |
domain | Dominio para el que la cookie es válida | '.example.com' |
De entre estas propiedades, httpOnly y secure son las más importantes en términos de seguridad.
Seguridad: cookies seguras
httpOnly
// Mal — Acceso a cookies posible mediante JavaScript (vulnerable a XSS)
res.cookie('token', 'abc123');
// Bien — No accesible mediante JavaScript
res.cookie('token', 'abc123', { httpOnly: true });Si se establece httpOnly: true, no se puede leer con document.cookie. Incluso si un atacante de XSS inyecta un script, no podrá robar las cookies. Debe establecerse obligatoriamente para las cookies relacionadas con la autenticación.
secure
res.cookie('token', 'abc123', {
httpOnly: true,
secure: true // Solo se transmite en HTTPS
});En las conexiones HTTP (no cifradas), las cookies no se transmiten. Un atacante de tipo "man-in-the-middle" (MITM) no puede interceptar las cookies.
sameSite
res.cookie('token', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'strict' // Solo se transmite en solicitudes del mismo sitio
});| Valor | Comportamiento |
|---|---|
'strict' | Incluye las cookies solo en las solicitudes que se originan en el mismo sitio. Bloqueo completo de CSRF. Sin embargo, si se accede a través de un enlace externo, la sesión se cierra. |
'lax' | Permite las solicitudes GET (al hacer clic en enlaces) y bloquea las solicitudes POST. Equilibrio entre la mayoría de las defensas contra CSRF y la comodidad. Recomendado |
'none' | Incluye las cookies en todas las solicitudes. Requiere secure: true. Úsese solo para las API entre sitios. |
Cookies vs. Almacenamiento local
| Cookies | localStorage | |
|---|---|---|
| Envío al servidor | Se incluyen automáticamente en cada solicitud | No se envían |
| Capacidad | ~4 KB | ~5 MB |
| Caducidad | Pueden caducar automáticamente | Solo se pueden eliminar manualmente |
| Acceso | Si es httpOnly, el acceso mediante JavaScript está prohibido | Acceso solo mediante JavaScript |
| Uso | Autenticación, sesiones, configuración del servidor | Estado de la interfaz de usuario, caché, datos sin conexión |
La información que "el servidor debe conocer" se almacena en las cookies; la información que "solo el navegador necesita conocer" se almacena en localStorage.
Patrón práctico: Cookies para el modo oscuro
// Configuración del modo oscuro — aplicable al renderizar en el servidor
app.post('/settings/theme', (req, res) => {
const { theme } = req.body; // 'dark' or 'light'
res.cookie('theme', theme, {
maxAge: 365 * 24 * 60 * 60 * 1000, // 1 year
httpOnly: false, // Se necesita acceso desde JS para aplicar CSS
sameSite: 'lax'
});
res.json({ theme });
});
app.get('/', (req, res) => {
const theme = req.cookies.theme || 'light';
res.render('index', { theme });
});Las configuraciones que no son sensibles a la seguridad, como el modo oscuro, pueden establecerse en httpOnly: false. Sin embargo, el token de autenticación nunca debe configurarse en httpOnly: false.
Resumen clave
| Concepto | Resumen en una línea |
|---|---|
| HTTP sin estado | El servidor no recuerda las solicitudes anteriores |
| Cookie | Un pequeño fragmento de datos que el servidor asigna al cliente |
| Set-Cookie | Servidor → Cliente (cabecera de respuesta) |
| Cookie | Cliente → Servidor (cabecera de solicitud, automática) |
| httpOnly | Bloquea el acceso de JavaScript (defensa contra XSS) |
| secure | Solo se transmite por HTTPS |
| sameSite | Defensa contra CSRF (se recomienda lax) |
Las cookies son el mecanismo de gestión de estado más antiguo de la web, pero siguen siendo los más utilizados. Son la base de todo: sesiones, autenticación y preferencias del usuario. Si se omiten los atributos de seguridad (httpOnly, secure, sameSite), se queda expuesto a XSS y CSRF; por lo tanto, al configurar una cookie, siempre verifique primero estos tres elementos.
Lista de verificación práctica
Elementos que debe verificar obligatoriamente al configurar una cookie de autenticación:
res.cookie('session_token', token, {
httpOnly: true, // ✓ Defensa contra XSS — inaccesible desde JS
secure: true, // ✓ Solo HTTPS — bloquea transmisión en texto claro
sameSite: 'lax', // ✓ Defensa contra CSRF — bloquea POST entre sitios
maxAge: 3600000, // ✓ Configuración de expiración — evita cookies permanentes
path: '/' // ✓ Limitación de ruta — solo el alcance necesario
});Si se omite alguno de estos cinco elementos, se generará una vulnerabilidad de seguridad. Antes de implementar en producción, revise esta lista de verificación.