`Cookie` en TypeScript

`Cookie` en TypeScript

Este artículo explica Cookie en TypeScript.

Recorremos patrones prácticos para manejar cookies de forma segura y confiable tanto en el navegador como en el servidor.

YouTube Video

Cookie en TypeScript

Conceptos básicos de las cookies

Una cookie es un mecanismo para almacenar cadenas pequeñas (pares nombre=valor) en el cliente, creada mediante la cabecera HTTP Set-Cookie o document.cookie. Puedes controlar su comportamiento con atributos de seguridad (HttpOnly, Secure, SameSite, etc.).

Operaciones básicas en el navegador: document.cookie

A continuación, el ejemplo mínimo para escribir una cookie en el navegador. Crea una cookie añadiendo una cadena a document.cookie.

 1// Set a simple cookie that expires in 7 days.
 2// Note: Comments are in English per the user's preference.
 3const setSimpleCookie = (name: string, value: string) => {
 4  const days = 7;
 5  const expires = new Date(Date.now() + days * 86400_000).toUTCString();
 6  // name=value; Expires=...; Path=/
 7  document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; Expires=${expires}; Path=/`;
 8};
 9
10setSimpleCookie('theme', 'dark');
  • Este código crea una cookie que expira en 7 días. Es fácil de usar en el lado del navegador, pero como no puedes establecer HttpOnly, deberías evitar almacenar información sensible.

Lectura de cookies (navegador)

La siguiente función es una función auxiliar que recupera el valor de la cookie con un nombre especificado de document.cookie. document.cookie se devuelve como una cadena delimitada por un punto y coma y un espacio ('; '). Por lo tanto, la dividimos y buscamos la cookie deseada.

 1// Parse document.cookie and return value for given name or null if not found.
 2const getCookie = (name: string): string | null => {
 3  const cookies = document.cookie ? document.cookie.split('; ') : [];
 4  for (const cookie of cookies) {
 5    const [k, ...rest] = cookie.split('=');
 6    const v = rest.join('=');
 7    if (decodeURIComponent(k) === name) {
 8      return decodeURIComponent(v);
 9    }
10  }
11  return null;
12};
13
14// Example usage:
15const theme = getCookie('theme'); // => "dark" if set
16console.log('theme cookie:', theme);
  • Esta función decodifica tanto el nombre como el valor de la cookie para compararlos y recuperarlos de forma segura. Maneja nombres de cookies duplicados y caracteres especiales codificados, lo que la convierte en una implementación simple pero robusta.

Comprender y aplicar atributos de seguridad

Las cookies tienen varios atributos importantes, y cada uno controla la seguridad y el alcance de su comportamiento.

  • El atributo HttpOnly impide que la cookie sea accesible desde JavaScript, lo que ayuda a mitigar los ataques XSS (cross-site scripting). Ten en cuenta que no puedes establecer HttpOnly desde el lado del navegador.
  • El atributo Secure restringe el envío de cookies únicamente a través de HTTPS, reduciendo el riesgo de intercepción y manipulación.
  • El atributo SameSite controla si las cookies se envían con solicitudes entre sitios y ayuda a prevenir ataques CSRF (cross-site request forgery).
  • El atributo Path especifica el alcance de las rutas de solicitud para las que se envía la cookie, y el atributo Domain especifica el dominio donde la cookie es válida.
  • Al establecer el atributo Expires o Max-Age, puedes controlar la caducidad de la cookie.

Ejemplo de añadir SameSite / Secure en el navegador (lo que es posible)

HttpOnly no puede configurarse en el lado del cliente. A continuación se muestra un ejemplo de cómo añadir SameSite y Secure en el lado del cliente. Sin embargo, Secure solo surte efecto en páginas HTTPS.

 1// Set cookie with SameSite and Secure attributes (Secure only effective over HTTPS).
 2const setCookieWithAttributes = (name: string, value: string) => {
 3  const maxAge = 60 * 60 * 24 * 7; // 7 days in seconds
 4  // Note: HttpOnly cannot be set from JS; set it on server-side when you want to restrict JS access.
 5  document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; `
 6    + `Max-Age=${maxAge}; `
 7    + `Path=/; `
 8    + `SameSite=Lax; `
 9    + `Secure`;
10};
11
12setCookieWithAttributes('session_hint', 'true');
  • SameSite=Lax es una opción segura y común. Para restricciones más estrictas usa SameSite=Strict, pero las navegaciones legítimas desde sitios externos pueden dejar de funcionar.

Configurar cookies en el servidor (Set-Cookie) (Node / TypeScript)

En el servidor puedes añadir HttpOnly mediante la cabecera Set-Cookie, por lo que la gestión de sesiones debería hacerse idealmente en el lado del servidor. A continuación, un ejemplo usando el módulo http de Node.

 1// A minimal Node HTTP server in TypeScript that sets a secure HttpOnly cookie.
 2// This example uses built-in 'crypto' for a random session id.
 3import http from 'http';
 4import crypto from 'crypto';
 5
 6const server = http.createServer((req, res) => {
 7  if (req.url === '/login') {
 8    const sessionId = crypto.randomBytes(16).toString('hex');
 9    // Set cookie with HttpOnly, Secure, SameSite and Path
10    // Expires is optional — Max-Age preferred for relative lifetimes.
11    res.setHeader('Set-Cookie', `sid=${sessionId}; `
12      + `HttpOnly; `
13      + `Secure; `
14      + `SameSite=Strict; `
15      + `Path=/; `
16      + `Max-Age=3600`
17    );
18    res.writeHead(302, { Location: '/' });
19    res.end();
20    return;
21  }
22
23  res.writeHead(200, { 'Content-Type': 'text/plain' });
24  res.end('Hello\n');
25});
26
27server.listen(3000, () => {
28  console.log('Server running on http://localhost:3000');
29});
  • Cuando estableces el atributo HttpOnly en el lado del servidor, la cookie deja de ser accesible desde JavaScript, lo que dificulta que sea robada por ataques XSS (cross-site scripting). Además, al añadir el atributo Secure para que las cookies siempre se envíen mediante HTTPS, puedes evitar la intercepción y la manipulación y mejorar la seguridad de la comunicación.

Implementación de serialización/análisis de cookies (un auxiliar del lado del servidor)

A continuación se muestra una utilidad sencilla del lado del servidor que construye una cadena de cabecera Set-Cookie.

 1// Cookie serialization helper for server-side use.
 2// Returns a properly formatted Set-Cookie header value.
 3type CookieOptions = {
 4  path?: string;
 5  domain?: string;
 6  maxAge?: number;
 7  expires?: Date;
 8  httpOnly?: boolean;
 9  secure?: boolean;
10  sameSite?: 'Strict' | 'Lax' | 'None';
11};
12
13const serializeCookie = (name: string, value: string, opts: CookieOptions = {}): string => {
14  const parts: string[] = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
15
16  if (opts.maxAge != null) parts.push(`Max-Age=${Math.floor(opts.maxAge)}`);
17  if (opts.expires) parts.push(`Expires=${opts.expires.toUTCString()}`);
18  if (opts.domain) parts.push(`Domain=${opts.domain}`);
19  parts.push(`Path=${opts.path ?? '/'}`);
20  if (opts.httpOnly) parts.push('HttpOnly');
21  if (opts.secure) parts.push('Secure');
22  if (opts.sameSite) parts.push(`SameSite=${opts.sameSite}`);
23
24  return parts.join('; ');
25};
26
27// Example usage:
28const headerValue = serializeCookie('uid', 'abc123', {
29  httpOnly: true,
30  secure: true,
31  sameSite: 'Lax',
32  maxAge: 3600
33});
34console.log(headerValue);
35// => "uid=abc123; Max-Age=3600; Path=/; HttpOnly; Secure; SameSite=Lax"
  • Esta utilidad forma la base para crear Set-Cookie en un servidor personalizado. Las bibliotecas pueden manejar muchos más casos límite.

Prevenir la manipulación con firmas (HMAC)

Es peligroso almacenar valores importantes directamente en cookies. Presentamos un método que firma valores en el servidor para detectar manipulaciones. Aquí usamos HMAC-SHA256. En producción, también debe gestionar la revocación de tokens en el lado del servidor.

 1// A simple HMAC signing and verification helper for cookies.
 2// Signing prevents client from tampering cookie values.
 3import crypto from 'crypto';
 4
 5const SECRET = 'replace_with_env_secret'; // store in env var in production
 6
 7const signValue = (value: string) => {
 8  const hmac = crypto.createHmac('sha256', SECRET);
 9  hmac.update(value);
10  return `${value}.${hmac.digest('hex')}`;
11};
12
13const verifySignedValue = (signed: string): string | null => {
14  const idx = signed.lastIndexOf('.');
15  if (idx === -1) return null;
16  const value = signed.slice(0, idx);
17  const sig = signed.slice(idx + 1);
18
19  const hmac = crypto.createHmac('sha256', SECRET);
20  hmac.update(value);
21  const expected = hmac.digest('hex');
22  // use timing-safe comparison in production
23  if (crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
24    return value;
25  }
26  return null;
27};
28
29// Example usage:
30const signed = signValue('userId=42');
31console.log('signed:', signed);
32console.log('verified:', verifySignedValue(signed)); // => "userId=42"
  • Con este método, puede comprobar si el valor de la cookie ha sido manipulado. No incluya la clave de firma en el código fuente; gestiónela mediante medios seguros, como variables de entorno.

Cookies y mitigación de CSRF (patrones generales)

Las sesiones que usan cookies necesitan protección CSRF. Las mitigaciones típicas incluyen:.

  • Configura SameSite en Lax o Strict para evitar envíos entre sitios innecesarios. Utilice SameSite=None; Secure solo cuando la comunicación entre orígenes sea necesaria.
  • Utilice tokens CSRF, validados por el servidor, para formularios y solicitudes de API. No almacene el token en una cookie HttpOnly; en su lugar, proporciónelo mediante el cuerpo de la respuesta o etiquetas meta, y haga que JavaScript lo lea solo cuando sea necesario.
  • Si el token es accesible desde JavaScript, asegúrese de implementar también protecciones contra XSS (CSP, escapado de salida, etc.).
  • Exija la reautenticación para las acciones sensibles y en el inicio de sesión, y prevenga los ataques de fijación de sesión.

A continuación se muestra un ejemplo de cliente seguro que envía el token CSRF en un encabezado. Esto supone que el cliente ya ha recibido el token del servidor, p. ej., en el cuerpo de la respuesta.

 1// Example of sending CSRF token safely in a header using fetch.
 2// Assumes CSRF token was provided securely (e.g., via response body or meta tag).
 3async function sendProtectedRequest(url: string, csrfToken: string) {
 4  const res = await fetch(url, {
 5    method: 'POST',
 6    credentials: 'same-origin', // include cookies for same-site requests
 7    headers: {
 8      'Content-Type': 'application/json',
 9      'X-CSRF-Token': csrfToken
10    },
11    body: JSON.stringify({ action: 'doSomething' })
12  });
13  return res;
14}
  • Si especifica credentials: 'same-origin', solo se envían las cookies del mismo origen. En el servidor, valide el valor del encabezado X-CSRF-Token y verifique que el token coincida. Si son necesarias solicitudes entre sitios, configure CORS con cuidado.

Entre sitios (CORS) y la relación con credentials

Para enviar y recibir cookies en la comunicación entre orígenes, el cliente debe especificar credentials: 'include' y el servidor debe establecer Access-Control-Allow-Credentials: true. Sin embargo, restrinja esta configuración a orígenes de confianza y no use Access-Control-Allow-Origin: *.

 1async function fetchCrossOriginData() {
 2  // Example: cross-origin fetch sending cookies.
 3  // Server must set Access-Control-Allow-Credentials: true
 4  // and a specific trusted origin.
 5  const res = await fetch('https://api.example.com/data', {
 6    credentials: 'include', // send cookies only to trusted domains
 7    method: 'GET'
 8  });
 9  return res;
10}
  • La combinación de CORS y cookies es muy delicada desde el punto de vista de la seguridad. Gestione estrictamente los orígenes permitidos con una lista blanca y evite la comunicación entre sitios innecesaria. Además, al usar SameSite=None; Secure, exija HTTPS para prevenir ataques de intermediario (MITM).

Ejemplo: configuración segura de cookies de sesión con Express (TypeScript)

En la práctica, usa Express, la biblioteca cookie, express-session, entre otros. A continuación, un ejemplo sencillo usando express y cookie-parser. En la práctica, establece secure en true y gestiona secret mediante variables de entorno.

 1// Express example using cookie-parser and setting a secure httpOnly cookie.
 2// npm install express cookie-parser @types/express @types/cookie-parser
 3import express from 'express';
 4import cookieParser from 'cookie-parser';
 5
 6const app = express();
 7app.use(cookieParser(process.env.COOKIE_SECRET));
 8
 9app.post('/login', (req, res) => {
10  // authenticate user (omitted)
11  const sessionId = 'generated-session-id';
12  res.cookie('sid', sessionId, {
13    httpOnly: true,
14    secure: true,        // require HTTPS in production
15    sameSite: 'lax',
16    maxAge: 1000 * 60 * 60 // 1 hour
17  });
18  res.json({ ok: true });
19});
20
21app.listen(3000);
  • Al usar la función secret de cookieParser, puedes trabajar fácilmente con cookies firmadas. Sin embargo, en aplicaciones reales, desde las perspectivas de seguridad y escalabilidad, no deberías almacenar datos directamente en cookies; en su lugar, usa un almacén de sesiones dedicado.

Prefijos de cookies __Host- y __Secure-

Los navegadores aplican reglas especiales para ciertos prefijos.

  • El prefijo __Secure- Si un nombre de cookie empieza con __Secure-, el atributo Secure es obligatorio.
  • El prefijo __Host- Si empieza con __Host-, Secure es obligatorio, la ruta debe ser / (raíz) y no se debe establecer Domain.

Usarlos reduce la mala configuración y mejora la seguridad.

Buenas prácticas para cookies

Para manejar las cookies de forma segura, tenga en cuenta los siguientes puntos.

  • No almacenes información sensible directamente en cookies. Prefiere un ID de sesión del lado del servidor para los tokens de acceso.
  • Configura cookies de sesión con HttpOnly, Secure y SameSite=Lax (o Strict).
  • Aprovecha prefijos como __Host- y __Secure-.
  • Considera firmas (HMAC) y cifrado para prevenir manipulación y espionaje.
  • Habilita Secure y exige HTTPS.
  • Apunta al mínimo privilegio y a expiraciones cortas.
  • Usa tokens CSRF.
  • Cuidado con las diferencias en el comportamiento de SameSite entre navegadores, especialmente los más antiguos.

Conceptos erróneos comunes sobre las cookies

Con respecto a las cookies, ten en cuenta los siguientes malentendidos comunes.

  • 'Añadir HttpOnly elimina el impacto de XSS.' Si bien HttpOnly impide que JavaScript acceda a las cookies, XSS aún puede utilizarse para realizar solicitudes arbitrarias. También deberías emplear verificación de tokens CSRF, CSP (Content Security Policy) y sanitización de entradas.
  • 'Secure es innecesario para el desarrollo local.' Simular HTTPS y verificar el comportamiento incluso en entornos locales mejora la precisión de las pruebas. Como mínimo, se debe usar HTTPS en los entornos de staging y producción.
  • 'Los periodos de caducidad largos son convenientes.' Si estableces vidas útiles largas para las cookies, aumenta el periodo durante el cual pueden ser utilizadas indebidamente si son robadas. Puedes establecer caducidades más cortas e incorporar reautenticación periódica y rotación de tokens.

Resumen

Aunque las cookies son fáciles de usar, un manejo incorrecto puede introducir vulnerabilidades de seguridad. Para gestionarlas correctamente con TypeScript, es importante comprender atributos como HttpOnly, Secure y SameSite, y aplicar configuraciones seguras en el servidor. Al no almacenar datos sensibles directamente y combinar firmas con expiraciones cortas, puedes lograr una gestión de sesiones segura y confiable.

Puedes seguir el artículo anterior utilizando Visual Studio Code en nuestro canal de YouTube. Por favor, también revisa nuestro canal de YouTube.

YouTube Video