Gestión de Secrets en JavaScript Frontend
Aprende cómo manejar API keys, tokens de autenticación y otros secrets de forma segura en aplicaciones JavaScript frontend.
TL;DR - Resumen rápido
- El código JavaScript del frontend es SIEMPRE público y visible en DevTools
- NUNCA expongas API keys privadas (OpenAI, Stripe secrets) en código del cliente
- Usa domain restrictions para API keys públicas (Google Maps, Firebase)
- Implementa un backend proxy para ocultar secrets y controlar llamadas a APIs
- Almacena tokens en cookies httpOnly, no en localStorage (vulnerable a XSS)
Introducción a Secrets en Frontend
Un "secret" es cualquier información sensible que da acceso a recursos protegidos: API keys, tokens de autenticación, credenciales de servicios, etc. En el desarrollo frontend, el código JavaScript es siempre público y visible en las DevTools del navegador, el código fuente, y los bundles minificados. Esto significa que cualquier secret que incluyas en tu código del cliente puede ser extraído y usado por cualquiera.
A diferencia del backend donde puedes usar variables de entorno (process.env.API_KEY) protegidas en el servidor, el frontend no tiene un lugar seguro para guardar secrets. Todo el JavaScript se ejecuta en el navegador del usuario, donde puede ser inspeccionado, copiado y modificado. Esta realidad fundamental requiere estrategias diferentes para manejar información sensible.
El código frontend es siempre público
DevTools → Sources muestra todo tu código JavaScript. Bundlers como Webpack o Vite no ocultan secrets, solo comprimen el código. Minificación no es seguridad. Cualquier secret en el bundle puede ser extraído en segundos. Asume que todo el código del cliente es público.
El Problema de API Keys Expuestas
Exponer API keys privadas en el código frontend es uno de los errores de seguridad más comunes y costosos. Un atacante que obtiene tu API key de OpenAI, Stripe, AWS, o cualquier servicio de pago puede generar costos ilimitados a tu cuenta. Incluso si la key está en un archivo separado o "ofuscada" con atob(), sigue siendo trivialmente accesible en el bundle del cliente.
Los scanners automáticos buscan constantemente en GitHub y en sitios web públicos por API keys expuestas. En cuestión de minutos después de pushear una key privada, bots pueden detectarla y usarla para ataques o minería de criptomonedas. Servicios como OpenAI, AWS y Stripe envían alertas de seguridad cuando detectan keys expuestas públicamente, pero el daño puede ya estar hecho.
Este código muestra ejemplos de API keys expuestas en el frontend. Todas estas técnicas (constantes, archivos de configuración, ofuscación con atob()) son igualmente inseguras porque el código JavaScript del cliente es siempre accesible. La única solución es nunca incluir secrets privados en el código del cliente.
Secrets que NUNCA deben estar en frontend
OpenAI API keys (sk-...), Stripe secret keys (sk_live_...), AWS credentials, database passwords, private GitHub tokens, y cualquier key que comience con "secret" o "private". Estas keys dan acceso completo y pueden generar costos ilimitados.
Domain Restrictions: Cuándo es Seguro
Algunos servicios como Google Maps, Firebase y Mapbox usan API keys "públicas" diseñadas para ser expuestas en el frontend, pero protegidas mediante restricciones de dominio configuradas en la consola del servicio. Con HTTP Referrer restrictions, la API key solo funciona desde dominios autorizados como https://tudominio.com/*, bloqueando cualquier uso desde otros sitios.
Cuando configuras domain restrictions en Google Cloud Console o Firebase Console, el servicio verifica el header Referer de cada request HTTP. Si el request viene de un dominio no autorizado, es rechazado automáticamente. Esto hace seguro exponer la API key en tu código del cliente porque incluso si alguien copia la key, no puede usarla desde su propio sitio.
Este código muestra API keys de Google Maps, Firebase y Mapbox que son seguras de exponer porque están protegidas con domain restrictions. Sin embargo, siempre verifica en la consola del servicio que las restricciones estén correctamente configuradas antes de desplegar a producción.
- <strong>Google Maps API:</strong> Configura HTTP referrers en Google Cloud Console → APIs & Services → Credentials
- <strong>Firebase:</strong> Configura authorized domains en Firebase Console → Authentication → Settings
- <strong>Mapbox:</strong> Configura URL restrictions en Mapbox Account → Tokens
- <strong>reCAPTCHA:</strong> Las site keys son públicas y protegidas por dominio automáticamente
Backend como Proxy para Secrets
La solución más segura para usar APIs que requieren secrets privados es implementar un backend proxy. El frontend hace llamadas a tu propio servidor (endpoint como /api/openai/chat), y el backend hace la llamada real a la API externa con el secret guardado en variables de entorno. Esto mantiene el secret completamente fuera del código del cliente.
El backend proxy también te da control total: puedes implementar rate limiting para prevenir abuso, validar y sanitizar inputs del usuario antes de enviarlos a la API externa, cachear respuestas para reducir costos, y agregar logging para monitorear uso. El cliente solo puede llamar a tu endpoint, no directamente a la API externa.
Este código muestra el patrón completo de backend proxy. El frontend hace requests a /api/weather y /api/openai/chat sin conocer las API keys. El backend Express tiene los secrets en process.env y hace las llamadas reales a las APIs externas.
Ventajas del Backend Proxy
Rate limiting por usuario o IP, validación de inputs antes de enviar a la API externa, caching de respuestas frecuentes para reducir costos, logging y monitoreo de uso, y capacidad de cambiar proveedores de API sin modificar el frontend. El proxy es la arquitectura recomendada para cualquier API de pago.
Almacenamiento Seguro de Tokens
Los tokens de autenticación (JWT, session tokens) deben ser almacenados de forma que estén protegidos contra ataques XSS. Usar localStorage.setItem('token', jwt) es vulnerable porque cualquier script inyectado puede leer localStorage y robar el token. La solución más segura es usar cookies con el flag httpOnly: true, que hace que JavaScript no pueda acceder al token.
Cuando el backend establece una cookie con res.cookie('token', jwt, { httpOnly: true, secure: true, sameSite: 'strict' }), el navegador envía automáticamente la cookie en cada request a ese dominio usando credentials: 'include' en fetch. El token nunca toca el JavaScript del cliente, protegiéndolo completamente contra XSS.
Este código compara diferentes métodos de almacenamiento de tokens. localStorage y sessionStorage son vulnerables a XSS. Cookies httpOnly son la opción más segura. Memory storage (variable en memoria) también es seguro pero se pierde al refrescar la página. El patrón recomendado es refresh token en cookie httpOnly + access token de corta duración en memoria.
Comparación de Almacenamiento de Tokens
localStorage: ❌ Vulnerable a XSS, persiste entre sesiones. sessionStorage: ❌ Vulnerable a XSS, se limpia al cerrar tab. Cookies httpOnly: ✅ Protegido contra XSS, requiere backend. Memory: ✅ Protegido contra XSS, se pierde al refrescar. La mejor opción es cookies httpOnly combinado con tokens de corta duración.
Resumen: Gestión de Secrets en Frontend
Conceptos principales:
- •El código JavaScript del frontend es siempre público y visible
- •NUNCA expongas API keys privadas (OpenAI, Stripe, AWS) en el cliente
- •API keys públicas con domain restrictions son seguras (Google Maps, Firebase)
- •Backend proxy es la solución para usar APIs con secrets privados
- •Cookies httpOnly son más seguras que localStorage para tokens
Mejores prácticas:
- •Usa backend proxy para todas las APIs que requieren secrets privados
- •Configura domain restrictions en Google Maps, Firebase antes de exponer keys
- •Almacena tokens JWT en cookies httpOnly, no en localStorage
- •Implementa rate limiting en tu backend proxy para prevenir abuso
- •Nunca commitees secrets en Git, usa .env y .gitignore