Maîtriser l’Art de la Sécurité des Progressive Web Apps
Les Progressive Web Apps (PWAs) sont devenues le standard de facto pour offrir des expériences semblables à des applications sur le web ouvert. Leurs forces principales – capacité hors ligne, notifications push et installabilité – introduisent également de nouvelles surfaces d’attaque que les sites web traditionnels ne rencontrent que rarement. Cet article plonge profondément dans le cycle de vie de la sécurité d’une PWA, combinant le durcissement au niveau réseau, les protections à l’exécution et les bonnes pratiques opérationnelles. À la fin, vous disposerez d’une checklist applicable à n’importe quel projet, qu’il s’agisse d’un simple lecteur de nouvelles ou d’une plateforme e‑commerce complexe.
TL;DR : Sécurisez votre PWA avec HTTPS, appliquez une CSP stricte, isolez les service workers, validez toutes les entrées et adoptez une routine de surveillance continue.
1. Couche Transport – La Première Ligne de Défense
1.1 Appliquer HTTPS Partout
Tous les navigateurs modernes imposent HTTPS pour l’enregistrement des service workers. Cependant, certains actifs (par ex. des analyses tierces) peuvent encore être chargés via HTTP, créant des avertissements de contenu mixte et ouvrant la porte aux attaques de type homme‑du‑milieu (MITM).
Étapes clés :
- Obtenez un certificat TLS de confiance (Let’s Encrypt, Cloudflare, etc.).
- Redirigez toutes les requêtes
http://vershttps://via la configuration du serveur. - Activez HSTS (HTTP Strict Transport Security) pour forcer les navigateurs à utiliser HTTPS pendant une période prédéfinie.
# Exemple de fragment NGINX
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;
# Paramètres TLS …
}
Note : HSTS élimine les attaques de downgrading, mais nécessite une planification soigneuse car, une fois définie, les navigateurs refuseront de se connecter en HTTP pendant la durée spécifiée.
1.2 Épinglage de Certificat (Optionnel)
Pour les PWAs hautement sensibles – banque, dossiers de santé – envisagez l’épinglage de certificat via Public Key Pinning Extension for HTTP (HPKP). Bien que HPKP soit en cours de dépréciation à cause des risques de déploiement, vous pouvez obtenir une sécurité similaire avec l’en‑tête Expect‑CT qui impose la transparence des certificats.
Expect-CT: max-age=86400, enforce, report-uri="https://example.com/report"
2. Durcissement des Service Workers
Les service workers sont le cœur des capacités hors ligne d’une PWA. Ils s’exécutent dans un thread d’arrière‑plan avec des privilèges élevés, ce qui en fait une cible privilégiée pour les exploitations.
2.1 Limitation du Scope
Définissez le scope le plus restreint possible lors de l’enregistrement d’un service worker. Cela empêche le worker d’intercepter des requêtes hors de son périmètre prévu.
navigator.serviceWorker.register('/sw.js', { scope: '/app/' })
.then(reg => console.log('SW enregistré avec le scope :', reg.scope));
2.2 Vérifier l’Intégrité avec Subresource Integrity (SRI)
Si vous chargez le script d’un service worker depuis un CDN, protégez‑le avec SRI afin de garantir que le code récupéré correspond à un hash attendu.
<script src="https://cdn.example.com/sw.js"
integrity="sha384-abc123..."
crossorigin="anonymous"></script>
2.3 Politique de Sécurité du Contenu pour les Service Workers
Une CSP stricte peut empêcher le worker de charger des scripts malveillants ou de se connecter à des origines non prévues.
Content-Security-Policy: script-src 'self' https://trusted.cdn.com;
connect-src 'self' https://api.example.com;
Astuce : Utilisez la directive
worker-src(prise en charge par les navigateurs récents) pour préciser explicitement les origines autorisées des scripts de workers.
2.4 Gestion d’État et Validation du Cache
Ne faites jamais confiance aveuglément aux entrées du cache. Validez toujours la fraîcheur des réponses en cache, surtout pour les jetons d’authentification ou les données personnelles.
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (url.pathname.startsWith('/api/')) {
// Contourner le cache pour les données dynamiques
event.respondWith(fetch(event.request));
return;
}
// Stratégie cache‑first par défaut pour les ressources statiques
event.respondWith(
caches.match(event.request).then(cached => cached || fetch(event.request))
);
});
3. Protections à l’Exécution – CSP, Permissions et Sandbox
3.1 CSP Complète
Une CSP bien conçue réduit le risque d’Cross‑Site Scripting (XSS) et d’injection de données. Voici une politique d’exemple adaptée à une PWA typique :
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-srcinclut un hash pour les scripts inline afin d’éviterunsafe-inline.frame-ancestors 'none'désactive le clickjacking.connect-srcwhitelist les API et les points de terminaison WebSocket.
3.2 Permissions Policy (Ancien Feature‑Policy)
Restreignez les fonctionnalités puissantes du navigateur que les PWAs pourraient hériter accidentellement.
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=()
3.3 Attribut Sandbox pour le Contenu Incorporé
Si votre PWA intègre des iframes tierces, isolez‑les avec l’attribut sandbox afin d’empêcher l’exécution de scripts et la navigation hors contrôle.
<iframe src="https://maps.example.com"
sandbox="allow-scripts allow-same-origin"
loading="lazy"></iframe>
4. Authentification & Autorisation
4.1 Authentification Basée sur les Tokens JWT
Utilisez des JSON Web Tokens (JWT) pour une authentification sans état, mais ne les stockez jamais dans localStorage — préférez les cookies Secure, HttpOnly afin de limiter le vol via XSS.
Set-Cookie: token=eyJhbGciOi...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
4.2 Rotation des Refresh Tokens
Mettez en place une rotation des refresh tokens afin de limiter les dommages en cas de compromission d’un token.
- Le client envoie le refresh token.
- Le serveur le valide et délivre à la fois un nouveau access token et un nouveau refresh token.
- L’ancien refresh token est invalidé dans la base de données.
4.3 Mitigation CSRF
Même avec des cookies SameSite, utilisez des tokens anti‑CSRF pour les requêtes POST modifiant l’état.
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
Sur le serveur, vérifiez que le token correspond à la session.
5. Stockage Sécurisé des Données
Les PWAs peuvent stocker des données via IndexedDB, Cache API et Web Storage. Chacune de ces méthodes possède ses propres considérations de sécurité.
| Stockage | Adéquation | Conseils de sécurité |
|---|---|---|
| Cache API | Ressources statiques, repli hors ligne | Mettre en cache uniquement les ressources immuables. Utiliser des noms de cache versionnés pour purger les données obsolètes. |
| IndexedDB | Données structurées, préférences utilisateur | Chiffrer les champs sensibles côté client avec l’API Web Crypto. |
| LocalStorage / SessionStorage | Petites données non sensibles | Ne jamais y stocker de secrets. Les données sont accessibles à tout script présent sur la page. |
5.1 Exemple : Chiffrer des Données dans 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. Surveillance, Audit et Réponse aux Incidents
La sécurité n’est pas une configuration ponctuelle ; c’est un cycle continu.
6.1 Scans Automatisés
- Utilisez OWASP ZAP ou Burp Suite pour rechercher XSS, CSRF et en‑têtes de sécurité manquants.
- Intégrez les audits Lighthouse dans les pipelines CI/CD afin d’imposer la conformité PWA.
6.2 Journalisation à l’Exécution
Capturez les erreurs du service‑worker et transmettez‑les à une endpoint sécurisée pour analyse.
self.addEventListener('error', event => {
fetch('https://logs.example.com/collect', {
method: 'POST',
body: JSON.stringify({ message: event.message, stack: event.error.stack })
});
});
6.3 Plan d’Intervention
- Détecter : Alerte déclenchée par un trafic anormal ou un rapport de violation CSP.
- Contenir : Révoquer les JWT compromis, faire pivoter les clés API.
- Éradiquer : Corriger le code vulnérable, mettre à jour le cache du service‑worker.
- Récupérer : Déployer une nouvelle version, surveiller les récidives.
- Post‑mortem : Documenter la cause première, mettre à jour la checklist de sécurité.
7. Vue d’Ensemble – Couches de Sécurité dans une PWA
flowchart TB
subgraph "Couche Transport"
H["\"HTTPS\""]
S["\"HSTS\""]
end
subgraph "Service Worker"
SW["\"Limitation du Scope\""]
CS["\"Validation du Cache\""]
end
subgraph "Runtime"
CSP["\"Content Security Policy\""]
PP["\"Permissions Policy\""]
SAN["\"iFrames Sandboxés\""]
end
subgraph "Auth & Data"
JWT["\"Cookies Sécurisés\""]
REF["\"Rotation des Refresh\""]
ENC["\"Chiffrement IndexedDB\""]
end
subgraph "Surveillance"
SCAN["\"OWASP ZAP\""]
LOG["\"Journalisation Runtime\""]
end
H --> SW --> CSP --> JWT --> SCAN
S --> SW
CS --> PP
PP --> ENC
LOG --> SCAN
Le diagramme montre comment chaque contrôle de sécurité s’appuie sur le précédent, formant une architecture de défense en profondeur.
8. Checklist – Référence Rapide pour une PWA Sécurisée
- Servir tout le contenu via HTTPS avec HSTS activé.
- Enregistrer les service workers avec le scope le plus restreint possible.
- Appliquer Subresource Integrity pour tout script chargé à distance.
- Mettre en place une CSP stricte, incluant
worker-srcet des hashes de scripts. - Utiliser Permissions‑Policy pour désactiver les fonctionnalités du navigateur non utilisées.
- Stocker les tokens d’authentification dans des cookies Secure, HttpOnly ; jamais dans localStorage.
- Faire pivoter les refresh tokens et valider les tokens anti‑CSRF sur les requêtes modifiant l’état.
- Chiffrer les données sensibles stockées dans IndexedDB.
- Exécuter des scans OWASP ZAP automatisés à chaque PR.
- Configurer la journalisation en temps réel des erreurs du service‑worker.
- Maintenir un plan de réponse aux incidents à jour.
9. Questions Fréquemment Posées
Q1 : Les PWAs ont‑elles besoin d’un Service Worker pour être sécurisées ?
Non. Les mesures de sécurité comme HTTPS, CSP et les cookies sécurisés fonctionnent indépendamment. Cependant, le Service Worker représente la surface d’attaque runtime la plus exposée, il nécessite donc un durcissement supplémentaire.
Q2 : Puis‑je utiliser unsafe-inline dans ma CSP pour les styles ?
Il vaut mieux l’éviter. Privilégiez le hachage ou le nonce des styles inline, ou déplacez‑les dans des fichiers CSS externes.
Q3 : À quelle fréquence devrais‑je renouveler mes certificats TLS ?
Let’s Encrypt délivre des certificats de 90 jours ; automatisez le renouvellement. Pour les certificats auto‑signés ou commerciaux, visez une validité maximale d’un an.
10. Conclusion
Les Progressive Web Apps brouillent la frontière entre expériences natives et web, offrant des capacités puissantes qui élargissent également la surface d’attaque. En superposant la sécurité du transport, en restreignant les scopes des service workers, en appliquant une politique de contenu stricte et en instaurant des pratiques d’authentification robustes, vous pouvez protéger votre PWA sans sacrifier les performances.
Rappelez‑vous : la sécurité est un voyage, pas une destination. Gardez vos dépendances à jour, auditez régulièrement votre code et restez informé des nouvelles menaces. Avec les bonnes pratiques présentées ici, votre PWA résistera aux attaques web les plus courantes.