Mastering the Art of Progressive Web App Security
Progressive Web Apps (PWAs) have become the de‑facto standard for delivering app‑like experiences on the open web. Their core strengths—offline capability, push notifications, and installability—also introduce new attack surfaces that traditional web sites rarely face. This article dives deep into the security lifecycle of a PWA, combining network‑level hardening, runtime protections, and operational best practices. By the end, you’ll have a checklist you can apply to any project, whether it’s a simple news reader or a complex e‑commerce platform.
TL;DR: Secure your PWA with HTTPS, enforce strict CSP, sandbox service workers, validate all inputs, and adopt a continuous monitoring routine.
1. Transport Layer – The First Line of Defense
1.1 Enforce HTTPS Everywhere
All modern browsers enforce HTTPS for service worker registration. However, some assets (e.g., third‑party analytics) may still be loaded over HTTP, creating mixed‑content warnings and opening the door for man‑in‑the‑middle (MITM) attacks.
Key steps:
- Obtain a trusted TLS certificate (Let’s Encrypt, Cloudflare, etc.).
- Redirect all
http://requests tohttps://via server configuration. - Enable HSTS (HTTP Strict Transport Security) to force browsers to use HTTPS for a predefined period.
# 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 ...
}
Note: HSTS eliminates downgrade attacks, but it requires careful planning because once set, browsers will refuse to connect over HTTP for the duration.
1.2 Certificate Pinning (Optional)
For highly sensitive PWAs—banking, health records—consider certificate pinning via the Public Key Pinning Extension for HTTP (HPKP). Although HPKP is being deprecated due to deployment risks, you can achieve similar security with Expect‑CT headers that enforce Certificate Transparency.
Expect-CT: max-age=86400, enforce, report-uri="https://example.com/report"
2. Service Worker Hardening
Service workers are the heart of a PWA’s offline capabilities. They run in a background thread with elevated privileges, making them a prime target for exploitation.
2.1 Scope Limitation
Define the smallest possible scope when registering a service worker. This prevents the worker from intercepting requests outside its intended area.
navigator.serviceWorker.register('/sw.js', { scope: '/app/' })
.then(reg => console.log('SW registered with scope:', reg.scope));
2.2 Verify Integrity with Subresource Integrity (SRI)
If you load a service worker script from a CDN, protect it with SRI to ensure the fetched code matches an expected hash.
<script src="https://cdn.example.com/sw.js"
integrity="sha384-abc123..."
crossorigin="anonymous"></script>
2.3 Content Security Policy for Service Workers
A strict CSP can prevent the worker from loading malicious scripts or connecting to unintended origins.
Content-Security-Policy: script-src 'self' https://trusted.cdn.com;
connect-src 'self' https://api.example.com;
Tip: Use the
worker-srcdirective (supported in newer browsers) to explicitly whitelist worker script origins.
2.4 State Management and Cache Validation
Never trust cache entries blindly. Always validate the freshness of cached responses, especially for authentication tokens or personal data.
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. Runtime Protections – CSP, Permissions, and Sandbox
3.1 Full‑Featured CSP
A well‑crafted CSP reduces the risk of Cross‑Site Scripting (XSS) and Data Injection attacks. Below is an example policy tailored for a typical PWA:
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-srcincludes a hash for inline scripts to avoidunsafe-inline.frame-ancestors 'none'disables clickjacking.connect-srcwhitelists APIs and WebSocket endpoints.
3.2 Permissions Policy (Former Feature‑Policy)
Restrict powerful browser features that PWAs might inadvertently inherit.
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=()
3.3 Sandbox Attribute for Embedded Content
If your PWA embeds third‑party iframes, sandbox them to prevent script execution and navigation.
<iframe src="https://maps.example.com"
sandbox="allow-scripts allow-same-origin"
loading="lazy"></iframe>
4. Authentication & Authorization
4.1 Token‑Based Auth with JWT
Use JSON Web Tokens (JWT) for stateless authentication, but never store them in localStorage—use Secure, HttpOnly cookies to mitigate XSS theft.
Set-Cookie: token=eyJhbGciOi...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
4.2 Refresh Token Rotation
Implement rotating refresh tokens to limit the damage of a compromised token.
- Client sends refresh token.
- Server validates and issues a new access token and a new refresh token.
- Old refresh token is invalidated in the DB.
4.3 CSRF Mitigation
Even with same‑site cookies, use Anti‑CSRF tokens for state‑changing POST requests.
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
On the server, verify the token matches the session.
5. Secure Data Storage
PWAs can store data via IndexedDB, Cache API, and Web Storage. Each has its own security considerations.
| Storage | Suitability | Security Tips |
|---|---|---|
| Cache API | Static assets, offline fallback | Cache only immutable resources. Use versioned cache names to purge stale data. |
| IndexedDB | Structured data, user preferences | Encrypt sensitive fields client‑side with Web Crypto API. |
| LocalStorage / SessionStorage | Small, non‑sensitive data | Avoid storing secrets. Data is accessible to any script on the page. |
5.1 Example: Encrypting IndexedDB Data
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. Monitoring, Auditing, and Incident Response
Security is not a one‑time configuration; it’s a continuous cycle.
6.1 Automated Scanning
- Use OWASP ZAP or Burp Suite to scan for XSS, CSRF, and insecure headers.
- Integrate Lighthouse audits into CI/CD pipelines to enforce PWA compliance.
6.2 Runtime Logging
Capture service‑worker errors and forward them to a secure endpoint for analysis.
self.addEventListener('error', event => {
fetch('https://logs.example.com/collect', {
method: 'POST',
body: JSON.stringify({ message: event.message, stack: event.error.stack })
});
});
6.3 Incident Playbook
- Detect: Alert triggered by abnormal traffic or CSP violation reports.
- Contain: Revoke compromised JWTs, rotate API keys.
- Eradicate: Patch vulnerable code, update service‑worker cache.
- Recover: Deploy new version, monitor for recurrence.
- Post‑mortem: Document root cause, update security checklist.
7. Visual Overview – Security Layers in a PWA
flowchart TB
subgraph "Transport Layer"
H["\"HTTPS\""]
S["\"HSTS\""]
end
subgraph "Service Worker"
SW["\"Scope Limitation\""]
CS["\"Cache Validation\""]
end
subgraph "Runtime"
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 "Monitoring"
SCAN["\"OWASP ZAP\""]
LOG["\"Runtime Logging\""]
end
H --> SW --> CSP --> JWT --> SCAN
S --> SW
CS --> PP
PP --> ENC
LOG --> SCAN
The diagram illustrates how each security control builds on the previous one, forming a defense‑in‑depth architecture.
8. Checklist – Quick Reference for a Secure PWA
- Serve all content over HTTPS with HSTS enabled.
- Register service workers with the narrowest possible scope.
- Apply Subresource Integrity for any remotely loaded scripts.
- Enforce a strict CSP, including
worker-srcand script hashes. - Use Permissions‑Policy to disable unused browser features.
- Store auth tokens in Secure, HttpOnly cookies; never in localStorage.
- Rotate refresh tokens and validate CSRF tokens on state‑changing requests.
- Encrypt sensitive data in IndexedDB.
- Run automated OWASP ZAP scans on every PR.
- Set up real‑time logging of service‑worker errors.
- Maintain an incident response playbook.
9. Frequently Asked Questions
Q1: Do PWAs need a Service Worker for security?
No. Security measures like HTTPS, CSP, and secure cookies work independently. However, the Service Worker is where most runtime attacks surface, so it demands extra hardening.
Q2: Can I use unsafe-inline in CSP for styling?
Prefer avoiding it. Instead, hash or nonce the inline styles, or move them to external CSS files.
Q3: How often should I rotate TLS certificates?
Let’s Encrypt issues 90‑day certificates; automate renewal. For self‑signed or commercial certs, aim for a 1‑year validity at most.
10. Conclusion
Progressive Web Apps blur the line between native and web experiences, delivering powerful capabilities that also expand the attack surface. By layering transport security, tightening service‑worker scopes, enforcing a rigorous Content Security Policy, and instituting robust authentication practices, you can safeguard your PWA without sacrificing performance.
Remember: security is a journey, not a destination. Keep your dependencies up‑to‑date, audit your code regularly, and stay informed about emerging threats. With the practices outlined here, your PWA will stand strong against the most common web‑based attacks.