Dominar el Arte de la Seguridad en Aplicaciones Web Progresivas
Las Progressive Web Apps (PWAs) se han convertido en el estándar de facto para ofrecer experiencias similares a las de una aplicación en la web abierta. Sus fortalezas principales —funcionalidad offline, notificaciones push e instalabilidad— también introducen nuevas superficies de ataque que los sitios web tradicionales rara vez enfrentan. Este artículo profundiza en el ciclo de vida de la seguridad de una PWA, combinando endurecimiento a nivel de red, protecciones en tiempo de ejecución y mejores prácticas operativas. Al final, tendrás una lista de verificación que podrás aplicar a cualquier proyecto, ya sea un lector de noticias sencillo o una plataforma de comercio electrónico compleja.
TL;DR: Asegura tu PWA con HTTPS, impone una CSP estricta, aísla los service workers, valida todas las entradas y adopta una rutina de monitoreo continuo.
1. Capa de Transporte – La Primera Línea de Defensa
1.1 Imponer HTTPS en Todas Partes
Todos los navegadores modernos exigen HTTPS para el registro de service workers. Sin embargo, algunos recursos (p. ej., análisis de terceros) pueden seguir cargándose mediante HTTP, generando advertencias de contenido mixto y abriendo la puerta a ataques de hombre‑en‑el‑medio (MITM).
Pasos clave:
- Obtén un certificado TLS confiable (Let’s Encrypt, Cloudflare, etc.).
- Redirige todas las peticiones
http://ahttps://mediante la configuración del servidor. - Habilita HSTS (HTTP Strict Transport Security) para obligar a los navegadores a usar HTTPS durante un período predefinido.
# Example NGINX snippet
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# TLS settings ...
}
Nota: HSTS elimina los ataques de degradación, pero requiere planificación cuidadosa porque, una vez activado, los navegadores rechazarán conexiones HTTP durante el tiempo especificado.
1.2 Anclado de Certificado (Opcional)
Para PWAs altamente sensibles —banca, historiales médicos— considera anclado de certificado mediante la Extensión de Anclado de Clave Pública para HTTP (HPKP). Aunque HPKP está siendo descontinuado por los riesgos de despliegue, puedes lograr una seguridad similar con los encabezados Expect‑CT, que obligan a la Transparencia de Certificados.
Expect-CT: max-age=86400, enforce, report-uri="https://example.com/report"
2. Endurecimiento de Service Workers
Los service workers son el corazón de las capacidades offline de una PWA. Se ejecutan en un sub‑hilo con privilegios elevados, lo que los convierte en un objetivo privilegiado para la explotación.
2.1 Limitación del Alcance (Scope)
Define el scope más pequeño posible al registrar un service worker. Esto evita que el worker intercepte peticiones fuera de su área prevista.
navigator.serviceWorker.register('/sw.js', { scope: '/app/' })
.then(reg => console.log('SW registered with scope:', reg.scope));
2.2 Verificar Integridad con Subresource Integrity (SRI)
Si cargas el script de un service worker desde una CDN, protégelo con SRI para asegurarte de que el código recibido coincide con el hash esperado.
<script src="https://cdn.example.com/sw.js"
integrity="sha384-abc123..."
crossorigin="anonymous"></script>
2.3 Política de Seguridad de Contenidos para Service Workers
Una CSP estricta puede impedir que el worker cargue scripts maliciosos o se comunique con orígenes no deseados.
Content-Security-Policy: script-src 'self' https://trusted.cdn.com;
connect-src 'self' https://api.example.com;
Consejo: Usa la directiva
worker-src(compatible en navegadores más recientes) para permitir explícitamente solo los orígenes de scripts de workers.
2.4 Gestión del Estado y Validación de Caché
Nunca confíes ciegamente en las entradas de caché. Siempre valida la frescura de las respuestas almacenadas, sobre todo para tokens de autenticación o datos personales.
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (url.pathname.startsWith('/api/')) {
// Bypass cache for dynamic data
event.respondWith(fetch(event.request));
return;
}
// Default cache‑first strategy for static assets
event.respondWith(
caches.match(event.request).then(cached => cached || fetch(event.request))
);
});
3. Protecciones en Tiempo de Ejecución – CSP, Permisos y Sandbox
3.1 CSP Completa
Una CSP bien diseñada reduce el riesgo de Cross‑Site Scripting (XSS) y de Inyección de Datos. A continuación, un ejemplo de política adaptada a una PWA típica:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'sha256-XYZ' https://analytics.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com wss://socket.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
script-srcincluye un hash para scripts inline y evitaunsafe-inline.frame-ancestors 'none'desactiva el clickjacking.connect-srcpermite solo APIs y puntos de WebSocket autorizados.
3.2 Permissions Policy (antes Feature‑Policy)
Restringe características poderosas del navegador que la PWA podría heredar sin necesidad.
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=()
3.3 Atributo Sandbox para Contenido Embebido
Si tu PWA incorpora iframes de terceros, aplícales sandbox para impedir la ejecución de scripts y la navegación no deseada.
<iframe src="https://maps.example.com"
sandbox="allow-scripts allow-same-origin"
loading="lazy"></iframe>
4. Autenticación y Autorización
4.1 Autenticación basada en Tokens con JWT
Utiliza JSON Web Tokens (JWT) para autenticación sin estado, pero nunca los almacenes en localStorage; emplea cookies seguras, HttpOnly para mitigar el robo mediante XSS.
Set-Cookie: token=eyJhbGciOi...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
4.2 Rotación de Refresh Tokens
Implementa refresh tokens rotativos para limitar el daño de un token comprometido.
- El cliente envía el refresh token.
- El servidor lo valida y emite un nuevo access token y un nuevo refresh token.
- El refresh token anterior se invalida en la base de datos.
4.3 Mitigación de CSRF
Aun con cookies SameSite, usa tokens anti‑CSRF para peticiones POST que cambian el estado.
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
En el servidor, verifica que el token coincida con la sesión.
5. Almacenamiento Seguro de Datos
Las PWAs pueden almacenar datos mediante IndexedDB, Cache API y Web Storage. Cada uno posee sus propias consideraciones de seguridad.
| Almacenamiento | Idoneidad | Consejos de Seguridad |
|---|---|---|
| Cache API | Recursos estáticos, fallback offline | Cachea solo recursos inmutables. Usa nombres de caché versionados para purgar datos obsoletos. |
| IndexedDB | Datos estructurados, preferencias de usuario | Encripta campos sensibles del lado del cliente con la Web Crypto API. |
| LocalStorage / SessionStorage | Datos pequeños y no sensibles | Evita guardar secretos. Los datos son accesibles a cualquier script en la página. |
5.1 Ejemplo: Encriptar Datos en IndexedDB
async function encryptAndStore(key, value) {
const cryptoKey = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encoded = new TextEncoder().encode(value);
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
cryptoKey,
encoded
);
const db = await openDB('secure-store', 1, {
upgrade(db) { db.createObjectStore('secrets'); }
});
await db.put('secrets', { iv, ciphertext }, key);
}
6. Monitoreo, Auditoría y Respuesta a Incidentes
La seguridad no es una configuración única; es un ciclo continuo.
6.1 Escaneo Automatizado
- Usa OWASP ZAP o Burp Suite para buscar XSS, CSRF y encabezados inseguros.
- Integra auditorías de Lighthouse en tus pipelines CI/CD para imponer el cumplimiento de PWA.
6.2 Registro en Tiempo de Ejecución
Captura errores del service worker y envíalos a un endpoint seguro para su análisis.
self.addEventListener('error', event => {
fetch('https://logs.example.com/collect', {
method: 'POST',
body: JSON.stringify({ message: event.message, stack: event.error.stack })
});
});
6.3 Playbook de Incidentes
- Detectar: Alerta disparada por tráfico anómalo o informes de violaciones CSP.
- Contener: Revocar JWT comprometidos, rotar claves de API.
- Erradicar: Parchear código vulnerable, actualizar caché del service worker.
- Recuperar: Desplegar nueva versión, monitorizar para recurrencias.
- Post‑mortem: Documentar la causa raíz y actualizar la lista de verificación de seguridad.
7. Visión General – Capas de Seguridad en una PWA
flowchart TB
subgraph "Capa de Transporte"
H["\"HTTPS\""]
S["\"HSTS\""]
end
subgraph "Service Worker"
SW["\"Scope Limitation\""]
CS["\"Cache Validation\""]
end
subgraph "Tiempo de Ejecución"
CSP["\"Content Security Policy\""]
PP["\"Permissions Policy\""]
SAN["\"Sandboxed iFrames\""]
end
subgraph "Auth & Data"
JWT["\"Secure Cookies\""]
REF["\"Refresh Rotation\""]
ENC["\"IndexedDB Encryption\""]
end
subgraph "Monitoreo"
SCAN["\"OWASP ZAP\""]
LOG["\"Runtime Logging\""]
end
H --> SW --> CSP --> JWT --> SCAN
S --> SW
CS --> PP
PP --> ENC
LOG --> SCAN
El diagrama muestra cómo cada control de seguridad se apoya en el anterior, formando una arquitectura de defensa en profundidad.
8. Lista de Verificación – Referencia Rápida para una PWA Segura
- Servir todo el contenido mediante HTTPS con HSTS habilitado.
- Registrar service workers con el
scopemás restrictivo posible. - Aplicar Subresource Integrity a cualquier script cargado remotamente.
- Imponer una CSP estricta, incluyendo
worker-srcy hashes de scripts. - Utilizar Permissions‑Policy para desactivar funcionalidades de navegador no usadas.
- Almacenar tokens de autenticación en cookies Secure, HttpOnly; nunca en
localStorage. - Rotar refresh tokens y validar tokens anti‑CSRF en peticiones que modifican el estado.
- Encriptar datos sensibles en IndexedDB.
- Ejecutar escaneos OWASP ZAP automáticos en cada Pull Request.
- Configurar registro en tiempo real de errores del service worker.
- Mantener un playbook de respuesta a incidentes actualizado.
9. Preguntas Frecuentes
P1: ¿Una PWA necesita un Service Worker para ser segura?
No. Medidas como HTTPS, CSP y cookies seguras funcionan de forma independiente. Sin embargo, el Service Worker es donde aparecen la mayoría de los vectores de ataque en tiempo de ejecución, por lo que requiere un endurecimiento adicional.
P2: ¿Puedo usar unsafe-inline en CSP para estilos?
Lo ideal es evitarlo. En su lugar, emplea hashes o nonces para los estilos inline, o trasládalos a archivos CSS externos.
P3: ¿Con qué frecuencia debo rotar los certificados TLS?
Let’s Encrypt emite certificados de 90 días; automatiza su renovación. Para certificados auto‑firmados o comerciales, no excedas un año de validez.
10. Conclusión
Las Progressive Web Apps borran la línea entre experiencias nativas y web, ofreciendo capacidades poderosas que también amplían la superficie de ataque. Al combinar seguridad en el transporte, restringir los alcances de los service workers, aplicar una política de seguridad de contenidos rigurosa y adoptar prácticas robustas de autenticación, puedes proteger tu PWA sin sacrificar rendimiento.
Recuerda: la seguridad es un viaje, no un destino. Mantén tus dependencias actualizadas, audita tu código con regularidad y permanece al tanto de las amenazas emergentes. Con las prácticas descritas aquí, tu PWA permanecerá firme frente a los ataques web más comunes.