12.2 — Sécurité frontend
🎯 Objectif : protéger les utilisateurs contre les attaques côté navigateur : XSS qui vole les sessions, CSRF qui exécute des actions à leur insu, clickjacking qui les manipule.
À l'issue de cet axe, tu sauras :
- Distinguer XSS stocké, réfléchi, DOM
- Configurer une Content Security Policy (CSP) stricte
- Comprendre CSRF et savoir s'en protéger (SameSite + tokens)
- Activer HSTS, X-Frame-Options, et autres en-têtes de sécu
- Valider et échapper les inputs dans toutes les contextes (HTML, attributs, JS, URLs)
Confirmé
XSS — Cross-Site Scripting
Section intitulée « XSS — Cross-Site Scripting »L’attaque n°1 côté frontend. Un attaquant injecte du JS qui s’exécute dans le navigateur d’autres utilisateurs.
3 types de XSS
Section intitulée « 3 types de XSS »1. XSS stocké (le plus dangereux)
Section intitulée « 1. XSS stocké (le plus dangereux) »Un commentaire utilisateur <script>steal(document.cookie)</script> est sauvegardé en DB et affiché à tous les visiteurs sans échappement.
// ❌const html = `<div>${userComment}</div>`;res.send(html);2. XSS réfléchi
Section intitulée « 2. XSS réfléchi »L’input arrive dans l’URL et est renvoyé tel quel.
https://example.com/search?q=<script>steal()</script>// ❌res.send(`<h1>Résultats pour ${req.query.q}</h1>`);3. XSS DOM-based
Section intitulée « 3. XSS DOM-based »Côté client, du JS prend une valeur de l’URL et l’injecte dans le DOM sans échapper.
// ❌document.getElementById('greeting').innerHTML = location.hash.slice(1);// URL : example.com/#<img src=x onerror=alert(1)>- Vol de cookies / tokens (si pas HttpOnly).
- Actions au nom de la victime (changer son mot de passe).
- Phishing dans la page.
- Crypto-mining dans le navigateur.
Prévention
Section intitulée « Prévention »A. Échapper systématiquement
Section intitulée « A. Échapper systématiquement »Les frameworks modernes (React, Vue, Svelte) échappent par défaut :
// ✅ React — échappe automatiquement<div>{userComment}</div>Le danger : dangerouslySetInnerHTML (React), v-html (Vue), innerHTML (vanilla). Si tu DOIS afficher du HTML utilisateur, assainis avec DOMPurify :
import DOMPurify from 'isomorphic-dompurify';
const clean = DOMPurify.sanitize(userHtml);return <div dangerouslySetInnerHTML={{ __html: clean }} />;B. Échapper selon le contexte
Section intitulée « B. Échapper selon le contexte »Le même input s’échappe différemment selon où il finit :
| Contexte | Échappement |
|---|---|
| Texte HTML | < → <, > → >, etc. |
| Attribut HTML | échapper guillemets aussi |
| URL | encodeURIComponent |
| JavaScript | JSON.stringify |
| CSS | éviter, c’est complexe |
Les frameworks gèrent ça correctement par défaut.
C. Content Security Policy (CSP)
Section intitulée « C. Content Security Policy (CSP) »Défense en profondeur : même si une XSS passe, la CSP bloque l’exécution du JS injecté.
Content-Security-Policy: default-src 'self'; script-src 'self' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.stripe.com; frame-ancestors 'none'; base-uri 'self';Le navigateur refuse d’exécuter tout script qui n’est pas du domaine autorisé.
CSP avec nonce (Next.js, Astro)
Section intitulée « CSP avec nonce (Next.js, Astro) »import { NextResponse } from 'next/server';
export function middleware(request) { const nonce = crypto.randomUUID(); const csp = `script-src 'self' 'nonce-${nonce}'; ...`; const response = NextResponse.next(); response.headers.set('Content-Security-Policy', csp); response.headers.set('x-nonce', nonce); return response;}// Layoutconst nonce = (await headers()).get('x-nonce');<script nonce={nonce}>console.log('OK')</script>Le nonce change à chaque requête → le JS injecté ne peut pas le deviner.
CSRF — Cross-Site Request Forgery
Section intitulée « CSRF — Cross-Site Request Forgery »Un site malveillant fait faire une requête à ton site au nom de la victime connectée.
L’utilisateur est connecté à banque.com. Il visite mauvais-site.com qui contient :
<form action="https://banque.com/transfer" method="POST"> <input type="hidden" name="to" value="hacker" /> <input type="hidden" name="amount" value="1000" /></form><script>document.forms[0].submit();</script>Le navigateur envoie automatiquement le cookie de banque.com → la banque exécute le transfert.
Protections (3 couches)
Section intitulée « Protections (3 couches) »1. Cookies SameSite=Lax ou Strict
Section intitulée « 1. Cookies SameSite=Lax ou Strict »Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=LaxSameSite=Lax = le cookie n’est PAS envoyé sur des POST cross-site. Protection automatique dans tous les navigateurs récents.
2. Token CSRF
Section intitulée « 2. Token CSRF »Le serveur génère un token unique par session, embedded dans les formulaires. Le serveur le vérifie sur chaque POST.
Avec Next.js (Server Actions) : protection CSRF native via le mécanisme de signature.
Avec Express / Hono : libs comme csurf, ou utiliser le pattern Double-Submit Cookie.
3. Header personnalisé
Section intitulée « 3. Header personnalisé »Pour les API : ajouter un header X-Requested-With: XMLHttpRequest. Les requêtes cross-origin ne peuvent pas ajouter de headers custom (sauf preflight CORS).
Verdict 2026
Section intitulée « Verdict 2026 »Si tu utilises :
- Cookies HttpOnly Secure SameSite=Lax + frameworks modernes (Next.js, Laravel, Symfony) → CSRF géré.
- JWT en Authorization Bearer (jamais cookie) → pas de CSRF (pas envoyé auto par le navigateur).
Clickjacking
Section intitulée « Clickjacking »L’attaquant met ton site dans une iframe invisible et fait cliquer la victime à des endroits piégés.
Protection
Section intitulée « Protection »X-Frame-Options: DENYOu (plus moderne, plus précis) :
Content-Security-Policy: frame-ancestors 'none';Le navigateur refuse de charger ton site dans une iframe.
En-têtes de sécurité — la liste complète
Section intitulée « En-têtes de sécurité — la liste complète »# Forcer HTTPS pour 1 an, incluant sous-domaines, soumis pour preloadStrict-Transport-Security: max-age=31536000; includeSubDomains; preload
# Empêche l'iframe (clickjacking)X-Frame-Options: DENY
# Désactive le sniffing de type MIMEX-Content-Type-Options: nosniff
# CSP — voir plus hautContent-Security-Policy: ...
# Référer leak controlReferrer-Policy: strict-origin-when-cross-origin
# Permissions modernes (caméra, micro, géoloc…)Permissions-Policy: camera=(), microphone=(), geolocation=()
# CORS (côté API)Access-Control-Allow-Origin: https://app.example.comAccess-Control-Allow-Credentials: truesecurityheaders.com → tape ton URL, obtiens un score A+ à F. Cible A minimum en prod.
Subresource Integrity (SRI)
Section intitulée « Subresource Integrity (SRI) »Si tu charges un script depuis un CDN tiers, vérifie le hash :
<script src="https://cdn.example.com/lib.js" integrity="sha384-Xxx..." crossorigin="anonymous"></script>Si le CDN est compromis et le fichier modifié, le navigateur refuse de l’exécuter.
integrity est calculé via openssl dgst -sha384 -binary lib.js | openssl base64 -A.
Mixed content
Section intitulée « Mixed content »Une page HTTPS qui charge des ressources HTTP est bloquée par défaut depuis 2020+ pour les scripts/iframes (active mixed content).
Détection : DevTools console → message en rouge.
Migration en bloc :
Content-Security-Policy: upgrade-insecure-requestsStorage côté client — récap sécu
Section intitulée « Storage côté client — récap sécu »| Storage | Accessible JS ? | Envoyé serveur ? | Conseil |
|---|---|---|---|
| Cookie HttpOnly Secure SameSite=Lax | ❌ | ✅ | Standard pour sessions/JWT |
| localStorage | ✅ | ❌ | OK pour préférences UI, PAS pour tokens |
| sessionStorage | ✅ | ❌ | Même règle que localStorage |
| IndexedDB | ✅ | ❌ | Pour gros volumes data offline |
Règle absolue : un token de session/auth dans localStorage → vol par XSS = accès complet au compte.
Auto-évaluation
Section intitulée « Auto-évaluation »Pour aller plus loin
Section intitulée « Pour aller plus loin »- OWASP XSS Cheat Sheet — cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- Content Security Policy Reference — content-security-policy.com
- Security Headers — securityheaders.com
- SameSite Cookies Explained — web.dev/articles/samesite-cookies-explained
- DOMPurify — github.com/cure53/DOMPurify
Suite : 12.3 — Sécurité backend — injections, IDOR, secrets, rate-limit.