Blog técnico

Volver al blog

¿Es seguro guardar datos en localStorage? Qué no deberías almacenar

LocalStorage funciona bien para preferencias como el tema de una web. El problema aparece cuando se usa para guardar sesiones, tokens o información que JavaScript no debería poder leer.

Publicado
Lectura
9 minutos
Temas
Seguridad · JavaScript · LocalStorage

En este portfolio uso localStorage para una decisión muy pequeña. Recordar si alguien eligió el tema claro u oscuro.

localStorage.setItem('theme', theme);

Si se borra ese valor, la web sigue funcionando. Si otra persona lo cambia en su navegador, no cambia nada importante. No identifica a nadie, no concede permisos y no permite entrar en una cuenta.

Ese detalle es el que me ayuda a decidir cuándo tiene sentido usar localStorage. No lo veo como una caja fuerte del navegador ni como algo que haya que evitar siempre. Es almacenamiento persistente al que puede acceder JavaScript dentro del mismo origen. Para una preferencia visual viene bien. Para una sesión o un secreto cambia completamente el riesgo.

La respuesta corta es esta. localStorage sirve para datos que pueden vivir en el navegador sin asumir autenticación ni autorización. No debería guardar contraseñas, identificadores de sesión, tokens de acceso, refresh tokens ni información privada que no quieres exponer a cualquier script de tu página.

El tema de una web no se parece a una sesión

El valor que uso en el portfolio solo puede ser light o dark. El código lo lee al cargar la página y aplica el tema correspondiente.

const theme = localStorage.getItem('theme');

if (theme === 'light' || theme === 'dark') {
  document.documentElement.dataset.theme = theme;
}

Es un ejemplo limitado a propósito. Aunque alguien modifique ese dato desde DevTools, el resultado es cambiar colores. No hay un servidor que confíe en él para tomar una decisión sensible.

Con una sesión ocurre lo contrario. Un token de acceso puede permitir consultar datos, modificar una cuenta o actuar en nombre de alguien. Si lo guardas en localStorage, cualquier JavaScript que se ejecute en ese origen puede intentar leerlo.

Ahí no importa que el nombre de la clave sea poco evidente. Tampoco ayuda codificar el valor o esconderlo detrás de una función. El navegador ofrece esa información al JavaScript de la página porque así funciona la API.

El caso que cambia la conversación es XSS

La preocupación principal no es que cualquiera pueda abrir la consola del navegador y ver sus propios datos. Una persona siempre puede modificar lo que vive en su propio navegador.

El problema serio aparece si existe un XSS. Un script inyectado dentro de tu origen tiene el mismo acceso a localStorage que tu JavaScript legítimo. Puede leer valores, copiarlos fuera o modificar información que después tu interfaz intenta usar.

Por eso guardar autenticación ahí une dos decisiones que a veces se estudian por separado. Un XSS puede ser una vulnerabilidad de renderizado, pero el daño aumenta si encuentra un token listo para usar en el almacenamiento del navegador.

En XSS explicado para desarrolladores frontend hablaba de la diferencia entre mostrar datos como texto e interpretarlos como HTML. Aquí la pregunta llega después. Si algo consigue ejecutar código en mi página, ¿qué información tiene disponible?

No hay que asumir que una CSP arregla esta situación. Una política de contenido puede limitar ciertos scripts y es una capa útil. Pero no convierte un token accesible desde JavaScript en un dato seguro.

Qué guardaría y qué evitaría

La frontera no depende solo de si un valor “parece importante”. Depende de qué puede hacer alguien con él y de qué pasa si se copia, se modifica o se pierde.

Dato ¿Tiene sentido en localStorage? Motivo
Tema claro u oscuro Es una preferencia local y no da acceso a nada.
Estado de un aviso de cookies Sirve para recordar una elección de interfaz.
Última pestaña abierta Mejora la continuidad sin afectar a permisos.
Token de sesión o JWT No JavaScript puede leerlo si existe un XSS.
Refresh token No Puede prolongar o renovar una sesión comprometida.
Contraseña o PIN No El navegador no es el sitio para persistir credenciales.
Datos personales o de pago No Añaden exposición y no deberían depender de la seguridad del cliente.
Rol de usuario o precio de un producto No como fuente de verdad Un usuario puede modificarlo en su navegador.

La última fila no tiene que ver solo con confidencialidad. Un rol o un precio pueden no ser secretos, pero el servidor no puede confiar en un valor que llega desde el cliente. El navegador puede sugerir una acción. El servidor debe comprobar permisos, precios y reglas de negocio.

sessionStorage no arregla el problema de seguridad

sessionStorage dura menos. Normalmente se borra al cerrar la pestaña o la sesión de página. Eso puede ser útil si solo necesitas conservar un dato durante una interacción concreta.

Pero también está disponible para JavaScript del mismo origen. Cambiar de localStorage a sessionStorage reduce persistencia, no evita que un XSS lea el contenido mientras la página está abierta.

Lo usaría cuando el ciclo de vida del dato lo pida. Por ejemplo, para una preferencia temporal de una sesión de navegación. No lo elegiría como forma de proteger un token sensible.

Las cookies resuelven una parte distinta

Cuando una aplicación necesita una sesión, las cookies permiten enviar un identificador al servidor sin que el JavaScript de la página tenga que leerlo. Con HttpOnly, ese acceso queda bloqueado para scripts. Con Secure, la cookie solo viaja por HTTPS.

Eso reduce el riesgo de robo directo de la sesión ante XSS, aunque no hace desaparecer XSS ni el resto de problemas de autenticación. Las cookies también obligan a pensar en SameSite, en CSRF y en la configuración de cada flujo.

En CSRF explicado con formularios y cookies explico esa otra cara. Mover un token fuera de localStorage no es pulsar un botón de “modo seguro”. Cambia el modelo de amenazas y exige revisar otras decisiones.

La elección correcta depende de la arquitectura. Lo que sí evitaría es guardar un token en localStorage solo porque leerlo desde fetch resulta cómodo.

Revisar un proyecto sin entrar en pánico

Si heredas un frontend o llevas meses sin tocar un proyecto, empezaría por buscar localStorage.setItem y sessionStorage.setItem.

Después miraría tres cosas

  • Qué valor se guarda realmente.
  • Qué puede hacer alguien con ese valor.
  • Qué ocurriría si un script del mismo origen consigue leerlo o cambiarlo.

Un tema, un idioma o el estado de un componente no exigen la misma respuesta que una sesión. Separar ambos casos evita dos errores habituales. Guardar secretos por comodidad y, en el extremo contrario, convertir cualquier uso de Web Storage en una alarma de seguridad.

También revisaría qué ocurre al leer esos valores. El contenido del navegador puede estar manipulado, venir de una versión anterior de la aplicación o no tener el formato esperado. Validar light y dark antes de aplicar el tema es una comprobación pequeña, pero refleja la idea correcta. Los datos del cliente no son una fuente de verdad.

Por qué me parece una decisión de frontend

Puede parecer que esto pertenece solo al backend porque habla de sesiones. Pero la decisión suele empezar en frontend cuando alguien necesita persistir un valor y tiene a mano una llamada a localStorage.

Entender qué queda accesible desde JavaScript ayuda a no convertir una solución cómoda en una deuda de seguridad. También obliga a distinguir entre guardar algo para mejorar la experiencia y guardar algo que el sistema usa para confiar en una persona.

El tema del portfolio es una buena referencia para mí porque muestra un uso pequeño y comprobable. No demuestra que una aplicación completa sea segura. Sí recuerda que la pregunta correcta no es “¿puedo guardar esto?”. Es “¿qué pasa si cualquiera que ejecute JavaScript en mi origen puede leerlo o cambiarlo?”