{"id":124,"date":"2026-06-14T16:31:10","date_gmt":"2026-06-14T16:31:10","guid":{"rendered":"https:\/\/shattered.io\/it\/2026\/06\/14\/protezione-csrf-nodejs\/"},"modified":"2026-06-14T16:33:52","modified_gmt":"2026-06-14T16:33:52","slug":"protezione-csrf-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/it\/2026\/06\/14\/protezione-csrf-nodejs\/","title":{"rendered":"Protezione CSRF in Node.js: 12 Step [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Un attacco CSRF (Cross-Site Request Forgery) sfrutta la sessione attiva di un utente per eseguire azioni che l&#8217;utente non ha mai voluto: cambiare la password, trasferire denaro, eliminare un account. Il browser allega automaticamente i cookie di sessione a ogni richiesta verso il tuo dominio, anche quando la richiesta parte da un sito malevolo. Senza una protezione CSRF, la tua applicazione Node.js non riesce a distinguere una richiesta legittima da una falsificata.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Questo tutorial ti guida in 12 step concreti verso una protezione CSRF solida su <strong>Express 5<\/strong>, usando il pattern Signed Double Submit Cookie con il pacchetto <code>csrf-csrf<\/code>. Tempo stimato: circa 30 minuti. Alla fine avrai un progetto completo e funzionante, con form HTML protetti, una variante per SPA (React, Vue, Angular), gestione degli errori, test di attacco simulato e una checklist di difese a strati allineata alla OWASP CSRF Prevention Cheat Sheet 2025.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"cose-un-attacco-csrf-e-perche-colpisce-ogni-app-node-js\">Cos&#8217;\u00e8 un attacco CSRF e perch\u00e9 colpisce ogni app Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Un attacco CSRF funziona perch\u00e9 l&#8217;autenticazione basata su cookie \u00e8 <em>ambientale<\/em>. Quando ti autentichi su <code>banca.it<\/code>, il server imposta un cookie di sessione. Da quel momento il browser invia quel cookie a <code>banca.it<\/code> in ogni richiesta, indipendentemente da quale pagina abbia originato la richiesta. Se visiti una pagina malevola mentre la tua sessione su <code>banca.it<\/code> \u00e8 ancora valida, quella pagina pu\u00f2 inviare di nascosto una richiesta POST verso <code>banca.it<\/code> e il cookie viaggia insieme alla richiesta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ecco la versione pi\u00f9 semplice dell&#8217;attacco. Una pagina ostile contiene un form nascosto che si invia da solo al caricamento:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;!-- Pagina malevola: evil.example\/regalo.html --&gt;\n&lt;body onload=\"document.forms[0].submit()\"&gt;\n  &lt;form action=\"https:\/\/banca.it\/trasferisci\" method=\"POST\"&gt;\n    &lt;input type=\"hidden\" name=\"importo\" value=\"5000\"&gt;\n    &lt;input type=\"hidden\" name=\"destinatario\" value=\"IT60-CONTO-ATTACCANTE\"&gt;\n  &lt;\/form&gt;\n&lt;\/body&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La vittima non clicca nulla. Il form si invia automaticamente, il browser allega il cookie di sessione e il server, senza una protezione CSRF, esegue il trasferimento come se fosse legittimo. Lo stesso schema si realizza con un tag <code>&lt;img&gt;<\/code> per le richieste GET non protette, motivo per cui le richieste GET non devono mai modificare lo stato del server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La difesa fondamentale \u00e8 un segreto che il sito malevolo non pu\u00f2 conoscere n\u00e9 leggere: un <strong>token CSRF<\/strong> imprevedibile, legato alla sessione, che deve accompagnare ogni richiesta che cambia stato (POST, PUT, PATCH, DELETE). Il browser invia automaticamente i cookie, ma non pu\u00f2 fabbricare un token valido per un dominio diverso dal proprio, e la Same-Origin Policy impedisce a uno script cross-site di leggere il token. Su questo principio si basa l&#8217;intero tutorial.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequisiti-e-versioni-richieste\">Prerequisiti e versioni richieste<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prima di scrivere codice, allinea l&#8217;ambiente. Le versioni qui sotto sono lo standard di riferimento a giugno 2026. Usa una versione LTS di Node.js: le versioni dispari non ricevono supporto a lungo termine e non vanno in produzione.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Componente<\/th><th>Versione consigliata (2026)<\/th><th>Ruolo nel progetto<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>22 LTS (o 24 LTS)<\/td><td>Runtime; modulo <code>crypto<\/code> nativo per HMAC<\/td><\/tr><tr><td>Express<\/td><td>5.x<\/td><td>Framework HTTP e middleware<\/td><\/tr><tr><td>csrf-csrf<\/td><td>4.x<\/td><td>Pattern Signed Double Submit Cookie<\/td><\/tr><tr><td>cookie-parser<\/td><td>1.4.x<\/td><td>Lettura e firma dei cookie<\/td><\/tr><tr><td>express-session (opzionale)<\/td><td>1.18.x<\/td><td>Sessioni lato server, se usate<\/td><\/tr><tr><td>helmet (consigliato)<\/td><td>8.x<\/td><td>Header di sicurezza HTTP<\/td><\/tr><tr><td>npm<\/td><td>10.x o superiore<\/td><td>Gestione dipendenze<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Verifica subito la versione di Node con <code>node -v<\/code>. Se \u00e8 inferiore alla 22, aggiorna prima di proseguire: Express 5 richiede Node 18 o superiore e diverse correzioni di sicurezza sui cookie sono arrivate solo nelle versioni LTS recenti. Una conoscenza di base di JavaScript asincrono (Promise, <code>async\/await<\/code>) e del modello richiesta\/risposta di Express \u00e8 data per scontata.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Una nota importante sul pacchetto storico <code>csurf<\/code>: \u00e8 deprecato e abbandonato dal 2020. Non riceve aggiornamenti, non gestisce correttamente l&#8217;attributo <code>SameSite<\/code> moderno e ha problemi noti. Non usarlo in nessun progetto nuovo. Il sostituto mantenuto \u00e8 <code>csrf-csrf<\/code>, che useremo qui.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"i-tre-pattern-di-protezione-csrf-a-confronto\">I tre pattern di protezione CSRF a confronto<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Esistono tre famiglie di difesa contro gli attacchi CSRF, pi\u00f9 l&#8217;attributo cookie <code>SameSite<\/code> come barriera complementare. Capire le differenze ti permette di scegliere il pattern giusto per la tua architettura. Le applicazioni stateful con sessioni lato server tendono al Synchronizer Token Pattern; le API stateless e le SPA preferiscono il Double Submit Cookie firmato.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Pattern<\/th><th>Come funziona<\/th><th>Stato lato server<\/th><th>Caso d&#8217;uso ideale<\/th><th>Debolezza<\/th><\/tr><\/thead><tbody><tr><td>Synchronizer Token (STP)<\/td><td>Token unico per sessione salvato sul server, inserito in un campo nascosto o header<\/td><td>S\u00ec, richiede storage<\/td><td>App stateful con rendering server-side<\/td><td>Pi\u00f9 memoria e gestione dello stato<\/td><\/tr><tr><td>Double Submit Cookie<\/td><td>Token in un cookie e replicato nel body o header; il server confronta i due valori<\/td><td>No<\/td><td>API stateless e SPA<\/td><td>Vulnerabile se il token non \u00e8 firmato<\/td><\/tr><tr><td>Signed Double Submit<\/td><td>Come sopra ma il token \u00e8 firmato con HMAC e un segreto server<\/td><td>No<\/td><td>SPA e microservizi moderni<\/td><td>Resta sensibile all&#8217;XSS<\/td><\/tr><tr><td>SameSite cookie<\/td><td>Il browser limita l&#8217;invio del cookie nelle richieste cross-site<\/td><td>No<\/td><td>Difesa di base per ogni app<\/td><td>Supporto e comportamento variabili sui browser legacy<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">In questo tutorial implementiamo il <strong>Signed Double Submit Cookie<\/strong> tramite <code>csrf-csrf<\/code>. \u00c8 il compromesso migliore per le applicazioni Node.js moderne: non richiede storage di sessione dedicato, funziona per form tradizionali e SPA, e la firma HMAC impedisce a un attaccante di fabbricare un token valido anche conoscendo l&#8217;algoritmo. Lo abbineremo a <code>SameSite<\/code>, alla validazione dell&#8217;header <code>Origin<\/code> e a HTTPS per ottenere una difesa a strati, esattamente come raccomanda OWASP.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-1-2-inizializzare-il-progetto-express-5\">Step 1-2: Inizializzare il progetto Express 5<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Crea la cartella del progetto e inizializza npm. Useremo i moduli ES (<code>\"type\": \"module\"<\/code>) perch\u00e9 sono lo standard per i progetti Node.js nel 2026.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir csrf-demo &amp;&amp; cd csrf-demo\nnpm init -y\nnpm pkg set type=\"module\"\nnpm install express csrf-csrf cookie-parser helmet\nnpm install --save-dev nodemon<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aggiungi uno script di avvio nel <code>package.json<\/code> per ricaricare il server a ogni modifica durante lo sviluppo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ package.json (estratto)\n\"scripts\": {\n  \"dev\": \"nodemon app.js\",\n  \"start\": \"node app.js\"\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Crea ora <code>app.js<\/code> con uno scheletro Express 5 minimo. Express 5 introduce una gestione degli errori pi\u00f9 rigorosa e una migliore propagazione delle Promise rifiutate, quindi il codice asincrono \u00e8 pi\u00f9 sicuro per impostazione predefinita rispetto a Express 4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ app.js\nimport express from \"express\";\nimport helmet from \"helmet\";\nimport cookieParser from \"cookie-parser\";\n\nconst app = express();\n\napp.use(helmet());\napp.use(express.json());\napp.use(express.urlencoded({ extended: false }));\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () =&gt; {\n  console.log(`Server in ascolto su http:\/\/localhost:${PORT}`);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Avvia con <code>npm run dev<\/code>. Dovresti vedere il messaggio di ascolto sulla porta 3000. Questo \u00e8 il punto di partenza: un server che al momento non ha alcuna protezione CSRF. Nei prossimi step la aggiungiamo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-3-4-configurare-cookie-parser-e-il-segreto-csrf\">Step 3-4: Configurare cookie-parser e il segreto CSRF<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Il pattern Signed Double Submit ha bisogno di due cose: la capacit\u00e0 di leggere e scrivere cookie, e un segreto HMAC stabile con cui firmare i token. Registra <code>cookie-parser<\/code> prima della protezione CSRF, perch\u00e9 il middleware CSRF legge il token dal cookie.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ app.js (aggiunte)\nimport crypto from \"node:crypto\";\n\n\/\/ cookie-parser con un segreto per i cookie firmati\nconst COOKIE_SECRET = process.env.COOKIE_SECRET ||\n  crypto.randomBytes(32).toString(\"hex\");\napp.use(cookieParser(COOKIE_SECRET));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il segreto CSRF non deve mai essere scritto in chiaro nel codice. Generane uno forte (almeno 32 byte, 256 bit) e caricalo da una variabile d&#8217;ambiente. In sviluppo va bene generarlo a runtime, ma in produzione il segreto deve essere fisso e persistente, altrimenti tutti i token diventano invalidi a ogni riavvio del server.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Genera un segreto CSRF robusto da terminale\nnode -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"\n# Esempio di output:\n# 9f2c1a7b4e8d0c63a5f1b9d72e4c8a0f6b3d51e9c7a2f48b1d6e0a93c5f7b284<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Salva il valore generato in un file <code>.env<\/code> (mai versionato in git) come <code>CSRF_SECRET<\/code> e <code>COOKIE_SECRET<\/code>. In produzione, usa il gestore di segreti del tuo provider (ad esempio le variabili d&#8217;ambiente del servizio o un vault dedicato). Un segreto debole o hardcoded \u00e8 uno degli errori pi\u00f9 comuni che vanifica l&#8217;intera protezione CSRF.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-5-6-installare-e-configurare-csrf-csrf\">Step 5-6: Installare e configurare csrf-csrf<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ora il cuore della protezione. La funzione <code>doubleCsrf<\/code> di <code>csrf-csrf<\/code> restituisce un middleware e una funzione per generare il token. Configuriamola con un segreto HMAC, le opzioni del cookie e la logica di estrazione del token dalla richiesta.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ csrf.js\nimport { doubleCsrf } from \"csrf-csrf\";\n\nconst isProd = process.env.NODE_ENV === \"production\";\n\nexport const {\n  doubleCsrfProtection, \/\/ middleware da applicare alle rotte\n  generateCsrfToken,    \/\/ genera e imposta il token\n  invalidCsrfTokenError \/\/ errore tipizzato per la gestione\n} = doubleCsrf({\n  getSecret: () =&gt; process.env.CSRF_SECRET,\n  getSessionIdentifier: (req) =&gt; req.sessionID || req.ip,\n  cookieName: isProd ? \"__Host-psifi.x-csrf-token\" : \"x-csrf-token\",\n  cookieOptions: {\n    httpOnly: true,\n    sameSite: \"strict\",\n    secure: isProd,\n    path: \"\/\"\n  },\n  size: 32,\n  ignoredMethods: [\"GET\", \"HEAD\", \"OPTIONS\"],\n  getTokenFromRequest: (req) =&gt;\n    req.headers[\"x-csrf-token\"] || req.body?._csrf\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Analizziamo le opzioni principali. <code>getSecret<\/code> fornisce il segreto HMAC con cui il token viene firmato e verificato. <code>getSessionIdentifier<\/code> lega il token a un&#8217;identit\u00e0 (la sessione se presente, altrimenti l&#8217;IP) per evitare il riuso del token tra utenti diversi. <code>ignoredMethods<\/code> esclude i metodi sicuri e idempotenti: GET, HEAD e OPTIONS non devono mai cambiare stato, quindi non richiedono un token CSRF.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le <code>cookieOptions<\/code> sono critiche. <code>httpOnly: true<\/code> impedisce a JavaScript di leggere il cookie, riducendo l&#8217;impatto di un eventuale XSS. <code>sameSite: \"strict\"<\/code> dice al browser di non inviare il cookie nelle richieste cross-site. <code>secure: true<\/code> in produzione impone HTTPS. Il prefisso <code>__Host-<\/code> sul nome del cookie in produzione aggiunge una garanzia extra: il browser accetta quel cookie solo se \u00e8 impostato con <code>Secure<\/code>, <code>path=\/<\/code> e senza dominio esplicito.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La funzione <code>getTokenFromRequest<\/code> definisce dove il server cerca il token nella richiesta in arrivo: prima nell&#8217;header <code>x-csrf-token<\/code> (usato dalle SPA), poi nel campo nascosto <code>_csrf<\/code> del body (usato dai form HTML tradizionali). Questo doppio supporto rende lo stesso backend compatibile sia con i form che con le SPA.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-7-proteggere-le-rotte-che-cambiano-stato\">Step 7: Proteggere le rotte che cambiano stato<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Applica il middleware <code>doubleCsrfProtection<\/code> alle rotte che modificano lo stato. Hai due opzioni: applicarlo globalmente (e poi escludere le rotte pubbliche) oppure applicarlo per rotta. L&#8217;approccio per rotta \u00e8 pi\u00f9 esplicito e meno soggetto a errori, quindi \u00e8 quello che useremo.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ app.js (aggiunte)\nimport { doubleCsrfProtection, generateCsrfToken } from \".\/csrf.js\";\n\n\/\/ Endpoint per ottenere il token (usato da form e SPA)\napp.get(\"\/csrf-token\", (req, res) =&gt; {\n  const token = generateCsrfToken(req, res);\n  res.json({ csrfToken: token });\n});\n\n\/\/ Rotta protetta: cambia stato, richiede un token valido\napp.post(\"\/account\/email\", doubleCsrfProtection, (req, res) =&gt; {\n  const { nuovaEmail } = req.body;\n  \/\/ ... logica di aggiornamento ...\n  res.json({ ok: true, email: nuovaEmail });\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La rotta <code>GET \/csrf-token<\/code> genera il token, lo imposta nel cookie firmato e ne restituisce il valore al client. La rotta <code>POST \/account\/email<\/code> \u00e8 protetta: se arriva senza un token valido, il middleware blocca la richiesta prima che raggiunga la tua logica. Ogni endpoint POST, PUT, PATCH o DELETE che modifica dati deve avere <code>doubleCsrfProtection<\/code> nella sua catena di middleware.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Un principio da non violare mai: le rotte GET devono restare sicure e idempotenti. Se hai una rotta come <code>GET \/account\/elimina<\/code> che cancella dati, non c&#8217;\u00e8 token CSRF che tenga, perch\u00e9 un semplice tag <code>&lt;img src=\"...\"&gt;<\/code> su una pagina ostile la attiverebbe. Sposta sempre le azioni distruttive su POST o DELETE.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-8-servire-il-token-a-un-form-html\">Step 8: Servire il token a un form HTML<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Per un form tradizionale con rendering server-side, inietta il token in un campo nascosto. Aggiungiamo una rotta che genera il token e lo inserisce nell&#8217;HTML. Useremo template literals per semplicit\u00e0, ma in un progetto reale lo passeresti al tuo motore di template (EJS, Pug, Handlebars).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ app.js (aggiunte)\napp.get(\"\/profilo\", (req, res) =&gt; {\n  const token = generateCsrfToken(req, res);\n  res.send(`\n    &lt;!DOCTYPE html&gt;\n    &lt;html lang=\"it\"&gt;\n    &lt;body&gt;\n      &lt;h1&gt;Cambia email&lt;\/h1&gt;\n      &lt;form action=\"\/account\/email\" method=\"POST\"&gt;\n        &lt;input type=\"hidden\" name=\"_csrf\" value=\"${token}\"&gt;\n        &lt;input type=\"email\" name=\"nuovaEmail\" required&gt;\n        &lt;button type=\"submit\"&gt;Salva&lt;\/button&gt;\n      &lt;\/form&gt;\n    &lt;\/body&gt;\n    &lt;\/html&gt;\n  `);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Quando l&#8217;utente invia il form, il browser allega sia il cookie del token (firmato HMAC) sia il valore nel campo <code>_csrf<\/code>. Il middleware confronta i due e verifica la firma. Un sito malevolo pu\u00f2 forzare il browser a inviare il cookie, ma non conosce il valore del token da inserire nel body, e non pu\u00f2 leggerlo a causa della Same-Origin Policy. Per questo l&#8217;attacco fallisce.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Genera sempre un token fresco al rendering della pagina. Non riutilizzare lo stesso token statico tra pagine diverse e non incorporarlo nell&#8217;URL: gli URL finiscono nei log, nella cronologia del browser e nell&#8217;header <code>Referer<\/code>, esponendo il token.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-9-inviare-il-token-da-una-spa-fetch-e-axios\">Step 9: Inviare il token da una SPA (fetch e Axios)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le Single Page Application (React, Vue, Angular) non usano form server-side. Recuperano il token da un endpoint dedicato all&#8217;avvio e lo allegano a ogni richiesta che cambia stato tramite un header personalizzato. Questo approccio \u00e8 pi\u00f9 sicuro dei campi nascosti perch\u00e9 gli header personalizzati non possono essere impostati da uno script cross-site senza superare la Same-Origin Policy.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ client.js (frontend SPA)\n\/\/ 1. Recupera il token all'avvio dell'app\nasync function initCsrf() {\n  const res = await fetch(\"\/csrf-token\", { credentials: \"include\" });\n  const { csrfToken } = await res.json();\n  return csrfToken;\n}\n\n\/\/ 2. Allega il token a ogni richiesta che cambia stato\nasync function aggiornaEmail(csrfToken, nuovaEmail) {\n  const res = await fetch(\"\/account\/email\", {\n    method: \"POST\",\n    credentials: \"include\",\n    headers: {\n      \"Content-Type\": \"application\/json\",\n      \"x-csrf-token\": csrfToken\n    },\n    body: JSON.stringify({ nuovaEmail })\n  });\n  return res.json();\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;opzione <code>credentials: \"include\"<\/code> \u00e8 obbligatoria: senza di essa il browser non invia il cookie del token e la verifica fallisce sempre. Con Axios, puoi automatizzare l&#8217;allegato del token con un interceptor, cos\u00ec non devi ricordartene a ogni chiamata:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Axios: interceptor globale\nimport axios from \"axios\";\n\nconst api = axios.create({ withCredentials: true });\nlet csrfToken = null;\n\nexport async function setupCsrf() {\n  const { data } = await api.get(\"\/csrf-token\");\n  csrfToken = data.csrfToken;\n}\n\napi.interceptors.request.use((config) =&gt; {\n  if ([\"post\", \"put\", \"patch\", \"delete\"].includes(config.method)) {\n    config.headers[\"x-csrf-token\"] = csrfToken;\n  }\n  return config;\n});\n\nexport default api;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Conserva il token nello stato dell&#8217;applicazione (variabile di modulo, store Redux\/Pinia), non in <code>localStorage<\/code>. Il <code>localStorage<\/code> \u00e8 leggibile da qualsiasi script nella pagina, quindi un XSS lo esporrebbe immediatamente. Se il token scade o una richiesta restituisce un errore CSRF, richiama <code>setupCsrf()<\/code> per ottenerne uno nuovo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-10-difese-a-strati-con-samesite-origin-e-https\">Step 10: Difese a strati con SameSite, Origin e HTTPS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Un token CSRF \u00e8 la difesa primaria, ma OWASP raccomanda di non affidarsi a un solo controllo. Aggiungiamo tre barriere complementari che rendono l&#8217;attacco molto pi\u00f9 difficile anche in caso di errore di configurazione.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"cookie-samesite-sulla-sessione\">Cookie SameSite sulla sessione<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Imposta <code>SameSite=Strict<\/code> o <code>Lax<\/code> su tutti i cookie di sessione, non solo sul token CSRF. <code>Strict<\/code> blocca l&#8217;invio del cookie in qualsiasi richiesta cross-site. <code>Lax<\/code> lo consente solo nelle navigazioni di primo livello (GET su un link), offrendo un buon equilibrio tra sicurezza ed esperienza utente. Non usare mai <code>SameSite=None<\/code> senza <code>Secure<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"validazione-dellheader-origin\">Validazione dell&#8217;header Origin<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Come difesa secondaria, verifica l&#8217;header <code>Origin<\/code> (o in mancanza <code>Referer<\/code>) contro una lista di domini fidati. Le richieste senza un Origin valido vengono respinte prima ancora di controllare il token.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ originGuard.js\nconst ORIGINI_FIDATE = new Set([\n  \"https:\/\/app.tuodominio.it\",\n  \"https:\/\/tuodominio.it\"\n]);\n\nexport function originGuard(req, res, next) {\n  const metodiSicuri = [\"GET\", \"HEAD\", \"OPTIONS\"];\n  if (metodiSicuri.includes(req.method)) return next();\n\n  const origin = req.headers.origin ||\n    (req.headers.referer &amp;&amp; new URL(req.headers.referer).origin);\n\n  if (!origin || !ORIGINI_FIDATE.has(origin)) {\n    return res.status(403).json({ errore: \"Origin non valida\" });\n  }\n  next();\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Applica <code>originGuard<\/code> prima di <code>doubleCsrfProtection<\/code> sulle rotte sensibili. Questo doppio controllo \u00e8 particolarmente efficace contro attacchi che riescono a superare uno solo dei due meccanismi.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"https-obbligatorio\">HTTPS obbligatorio<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Senza HTTPS, un attaccante in posizione di rete pu\u00f2 leggere il cookie del token e l&#8217;intera protezione crolla. In produzione, imponi HTTPS, attiva il flag <code>secure<\/code> sui cookie e aggiungi l&#8217;header HSTS. Se non hai ancora un certificato, configurarne uno gratuito \u00e8 semplice e veloce.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-11-gestire-gli-errori-csrf-in-modo-pulito\">Step 11: Gestire gli errori CSRF in modo pulito<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Quando un token manca o non \u00e8 valido, <code>csrf-csrf<\/code> lancia un errore tipizzato. Devi intercettarlo con un gestore di errori Express per restituire una risposta 403 chiara, invece di esporre uno stack trace. Registra il gestore dopo tutte le rotte.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ app.js (in fondo, dopo le rotte)\nimport { invalidCsrfTokenError } from \".\/csrf.js\";\n\napp.use((err, req, res, next) =&gt; {\n  if (err === invalidCsrfTokenError || err.code === \"EBADCSRFTOKEN\") {\n    return res.status(403).json({\n      errore: \"Token CSRF mancante o non valido\",\n      azione: \"Ricarica la pagina e riprova\"\n    });\n  }\n  next(err);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Lato client, intercetta la risposta 403 e gestiscila con eleganza: richiedi un nuovo token e, se opportuno, riprova la richiesta una sola volta. Non mostrare mai il messaggio di errore grezzo all&#8217;utente finale e non loggare il valore del token nei log applicativi, perch\u00e9 renderebbe vana la sua segretezza.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-12-testare-la-protezione-csrf\">Step 12: Testare la protezione CSRF<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Una protezione non testata non \u00e8 una protezione. Verifichiamo tre scenari: una richiesta senza token deve fallire, una con token valido deve passare, e cookie e token incoerenti devono essere respinti. Usiamo <code>curl<\/code> per simulare le richieste.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># 1. Richiesta SENZA token: deve essere respinta con 403\ncurl -i -X POST http:\/\/localhost:3000\/account\/email \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"nuovaEmail\":\"test@example.it\"}'\n# Atteso: HTTP\/1.1 403 Forbidden\n# {\"errore\":\"Token CSRF mancante o non valido\", ...}\n\n# 2. Ottieni un token e salva i cookie\ncurl -c cookies.txt http:\/\/localhost:3000\/csrf-token\n# Output: {\"csrfToken\":\"a1b2c3....\"}\n\n# 3. Richiesta CON token valido e cookie: deve passare\ncurl -i -X POST http:\/\/localhost:3000\/account\/email \\\n  -b cookies.txt \\\n  -H \"Content-Type: application\/json\" \\\n  -H \"x-csrf-token: a1b2c3....\" \\\n  -d '{\"nuovaEmail\":\"test@example.it\"}'\n# Atteso: HTTP\/1.1 200 OK\n# {\"ok\":true,\"email\":\"test@example.it\"}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il primo comando deve restituire 403, confermando che le richieste prive di token vengono bloccate. Il terzo deve restituire 200, confermando che una richiesta legittima con cookie e header coerenti passa. Se inverti i due, ad esempio fornendo il token ma non il cookie (o viceversa), la richiesta deve fallire: il pattern Double Submit richiede entrambi.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Per un test automatizzato, puoi scrivere una suite con Supertest che esegue questi tre scenari in un test di integrazione. Includi sempre il caso negativo (token mancante) tra i test, perch\u00e9 \u00e8 quello che garantisce che la protezione sia davvero attiva e non solo presente nel codice.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"il-progetto-completo-funzionante\">Il progetto completo funzionante<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ecco l&#8217;<code>app.js<\/code> completo che mette insieme tutti gli step. Con i file <code>csrf.js<\/code> e <code>originGuard.js<\/code> mostrati sopra, questo \u00e8 un server Express 5 con protezione CSRF pronta per essere estesa.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ app.js (completo)\nimport express from \"express\";\nimport helmet from \"helmet\";\nimport cookieParser from \"cookie-parser\";\nimport crypto from \"node:crypto\";\nimport {\n  doubleCsrfProtection,\n  generateCsrfToken,\n  invalidCsrfTokenError\n} from \".\/csrf.js\";\nimport { originGuard } from \".\/originGuard.js\";\n\nconst app = express();\nconst isProd = process.env.NODE_ENV === \"production\";\n\napp.use(helmet());\napp.use(express.json());\napp.use(express.urlencoded({ extended: false }));\n\nconst COOKIE_SECRET = process.env.COOKIE_SECRET ||\n  crypto.randomBytes(32).toString(\"hex\");\napp.use(cookieParser(COOKIE_SECRET));\n\nif (isProd) app.set(\"trust proxy\", 1);\n\n\/\/ Endpoint token per SPA e form\napp.get(\"\/csrf-token\", (req, res) =&gt; {\n  res.json({ csrfToken: generateCsrfToken(req, res) });\n});\n\n\/\/ Form server-side\napp.get(\"\/profilo\", (req, res) =&gt; {\n  const token = generateCsrfToken(req, res);\n  res.send(`&lt;form action=\"\/account\/email\" method=\"POST\"&gt;\n    &lt;input type=\"hidden\" name=\"_csrf\" value=\"${token}\"&gt;\n    &lt;input type=\"email\" name=\"nuovaEmail\" required&gt;\n    &lt;button&gt;Salva&lt;\/button&gt;&lt;\/form&gt;`);\n});\n\n\/\/ Rotta protetta con difesa a strati\napp.post(\"\/account\/email\", originGuard, doubleCsrfProtection,\n  (req, res) =&gt; {\n    res.json({ ok: true, email: req.body.nuovaEmail });\n  });\n\n\/\/ Gestore errori CSRF\napp.use((err, req, res, next) =&gt; {\n  if (err === invalidCsrfTokenError || err.code === \"EBADCSRFTOKEN\") {\n    return res.status(403).json({ errore: \"Token CSRF non valido\" });\n  }\n  next(err);\n});\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () =&gt; console.log(`In ascolto su :${PORT}`));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La struttura del progetto resta minima: <code>app.js<\/code>, <code>csrf.js<\/code>, <code>originGuard.js<\/code>, un file <code>.env<\/code> con i segreti e <code>package.json<\/code>. Da qui puoi collegare un database, un sistema di sessioni con <code>express-session<\/code> o un frontend SPA senza modificare la logica di protezione.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"6-errori-comuni-nella-protezione-csrf\">6 errori comuni nella protezione CSRF<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Anche con la libreria giusta, alcuni errori ricorrenti vanificano la protezione. Ecco i sei pi\u00f9 diffusi e come evitarli.<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li><strong>Usare ancora csurf.<\/strong> \u00c8 deprecato dal 2020 e non gestisce <code>SameSite<\/code> in modo corretto. Migra a <code>csrf-csrf<\/code> o alla protezione nativa del tuo framework.<\/li><li><strong>Segreto CSRF hardcoded o debole.<\/strong> Un segreto breve o scritto nel codice permette di fabbricare token validi. Usa almeno 32 byte casuali da una variabile d&#8217;ambiente persistente.<\/li><li><strong>Azioni che cambiano stato su GET.<\/strong> Una rotta <code>GET \/elimina<\/code> \u00e8 attivabile con un tag <code>&lt;img&gt;<\/code> e nessun token pu\u00f2 proteggerla. Usa POST, PUT, PATCH o DELETE.<\/li><li><strong>Dimenticare credentials: &#8220;include&#8221;.<\/strong> Senza questa opzione il browser non invia il cookie del token e ogni richiesta SPA fallisce, spingendo a disabilitare la protezione per errore.<\/li><li><strong>Salvare il token in localStorage.<\/strong> \u00c8 leggibile da qualsiasi script. Un XSS ruberebbe il token. Tieni il token nello stato in memoria dell&#8217;app.<\/li><li><strong>SameSite=None senza Secure.<\/strong> Disattiva la protezione del browser ed espone il cookie. Se non ti serve il cross-site, usa <code>Strict<\/code> o <code>Lax<\/code>.<\/li><\/ol>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"risoluzione-dei-problemi-troubleshooting\">Risoluzione dei problemi (troubleshooting)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Quando la protezione CSRF non si comporta come previsto, la causa \u00e8 quasi sempre una di queste. La tabella collega il sintomo alla causa e alla soluzione.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Sintomo<\/th><th>Causa probabile<\/th><th>Soluzione<\/th><\/tr><\/thead><tbody><tr><td>Tutte le POST restituiscono 403<\/td><td>Il cookie del token non viene inviato<\/td><td>Aggiungi <code>credentials: \"include\"<\/code> (fetch) o <code>withCredentials: true<\/code> (Axios)<\/td><\/tr><tr><td>Funziona in locale, fallisce in produzione<\/td><td><code>secure: true<\/code> richiede HTTPS<\/td><td>Servi su HTTPS e imposta <code>trust proxy<\/code> dietro un reverse proxy<\/td><\/tr><tr><td>I token diventano invalidi dopo ogni deploy<\/td><td>Segreto CSRF generato a runtime<\/td><td>Fissa <code>CSRF_SECRET<\/code> in una variabile d&#8217;ambiente persistente<\/td><\/tr><tr><td>Cookie <code>__Host-<\/code> non impostato<\/td><td>Manca <code>Secure<\/code>, <code>path=\/<\/code> o c&#8217;\u00e8 un dominio esplicito<\/td><td>Rimuovi <code>domain<\/code>, imposta <code>path: \"\/\"<\/code> e <code>secure: true<\/code><\/td><\/tr><tr><td>Errore CORS prima del controllo CSRF<\/td><td>Origin non in lista o credenziali non abilitate<\/td><td>Configura CORS con <code>credentials: true<\/code> e origini esplicite<\/td><\/tr><tr><td>Il token nel form non viene letto<\/td><td><code>getTokenFromRequest<\/code> non guarda nel body<\/td><td>Verifica che cerchi <code>req.body._csrf<\/code> e che <code>urlencoded<\/code> sia attivo<\/td><\/tr><tr><td>Le richieste SPA passano ma i form no<\/td><td>Header presente, campo nascosto assente<\/td><td>Inserisci <code>&lt;input type=\"hidden\" name=\"_csrf\"&gt;<\/code> nel form<\/td><\/tr><tr><td>403 intermittente per alcuni utenti<\/td><td>Pi\u00f9 istanze server con segreti diversi<\/td><td>Condividi lo stesso <code>CSRF_SECRET<\/code> tra tutte le istanze<\/td><\/tr><tr><td>Test con curl falliscono sempre<\/td><td>Cookie e header non coerenti<\/td><td>Usa <code>-c<\/code>\/<code>-b<\/code> per salvare e reinviare i cookie<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Se il problema persiste, attiva un log temporaneo che stampi se l&#8217;header e il cookie del token sono entrambi presenti (senza loggarne il valore). Nella stragrande maggioranza dei casi scoprirai che uno dei due manca, e da l\u00ec la causa \u00e8 immediata.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"consigli-avanzati-per-la-protezione-csrf\">Consigli avanzati per la protezione CSRF<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Una volta che la protezione di base funziona, alcune tecniche aggiuntive alzano ulteriormente il livello di sicurezza, soprattutto per applicazioni con dati sensibili o requisiti di conformit\u00e0 come il GDPR e la direttiva NIS2.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Token per richiesta sulle azioni critiche.<\/strong> Per operazioni ad alto rischio (cambio password, trasferimenti, eliminazione account) genera un token monouso valido per una sola richiesta, oltre al token di sessione. Riduce la finestra di replay.<\/li><li><strong>Rotazione del segreto.<\/strong> Pianifica una rotazione periodica di <code>CSRF_SECRET<\/code> supportando due segreti contemporaneamente durante la transizione, cos\u00ec i token gi\u00e0 emessi restano validi fino alla scadenza.<\/li><li><strong>Rate limiting sugli endpoint sensibili.<\/strong> Abbina la protezione CSRF a un limitatore di frequenza per mitigare anche tentativi di brute-force e abusi automatizzati.<\/li><li><strong>Difesa contro l&#8217;XSS prima di tutto.<\/strong> Il Double Submit firmato resta vulnerabile a un XSS che legge il body o inietta richieste. Una Content Security Policy rigorosa con <code>helmet<\/code> e l&#8217;escaping dell&#8217;output sono prerequisiti, non optional.<\/li><li><strong>Protezione nativa del framework.<\/strong> Se usi Fastify, valuta <code>@fastify\/csrf-protection<\/code>, che offre sia il Synchronizer Token sia il Double Submit. Scegli sempre la protezione integrata quando disponibile.<\/li><li><strong>Audit periodico delle rotte.<\/strong> A ogni nuova rotta che cambia stato, verifica in code review che il middleware CSRF sia applicato. Una rotta dimenticata \u00e8 un buco.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Per una panoramica autorevole e sempre aggiornata sulle contromisure, consulta la <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html\" target=\"_blank\" rel=\"noopener\">OWASP CSRF Prevention Cheat Sheet<\/a> e la spiegazione tecnica dell&#8217;attacco su <a href=\"https:\/\/portswigger.net\/web-security\/csrf\" target=\"_blank\" rel=\"noopener\">PortSwigger Web Security Academy<\/a>. Per il comportamento dell&#8217;attributo cookie, il riferimento \u00e8 la documentazione <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Set-Cookie\/SameSite\" target=\"_blank\" rel=\"noopener\">MDN su SameSite<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"csrf-e-il-contesto-normativo-europeo\">CSRF e il contesto normativo europeo<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In Europa, una protezione CSRF carente non \u00e8 solo un problema tecnico ma anche di conformit\u00e0. Un attacco CSRF riuscito che porta alla modifica non autorizzata di dati personali pu\u00f2 configurare una violazione ai sensi del GDPR, con obbligo di notifica al Garante entro 72 ore e potenziali sanzioni. La direttiva NIS2, recepita in Italia, impone misure di sicurezza adeguate per i soggetti essenziali e importanti, e i controlli applicativi contro le richieste falsificate rientrano tra le buone pratiche attese.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Documentare la presenza di una protezione CSRF nelle tue applicazioni, insieme ai test che ne verificano l&#8217;efficacia, \u00e8 utile sia in fase di audit sia in caso di incidente. Una difesa a strati come quella di questo tutorial, token firmato HMAC pi\u00f9 <code>SameSite<\/code> pi\u00f9 validazione Origin pi\u00f9 HTTPS, \u00e8 esattamente il tipo di misura tecnica che dimostra diligenza nel trattamento dei dati. Per approfondire l&#8217;attacco dal punto di vista del difensore, la <a href=\"https:\/\/owasp.org\/www-community\/attacks\/csrf\" target=\"_blank\" rel=\"noopener\">scheda OWASP sul Cross-Site Request Forgery<\/a> resta il punto di partenza.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La protezione CSRF si integra con le altre difese applicative. Va vista insieme all&#8217;autenticazione robusta, alla gestione sicura delle sessioni e all&#8217;hashing corretto delle credenziali. Nessuna di queste misure, da sola, basta: insieme formano una postura di sicurezza coerente.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"domande-frequenti-sulla-protezione-csrf\">Domande frequenti sulla protezione CSRF<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"il-pacchetto-csurf-e-ancora-utilizzabile-nel-2026\">Il pacchetto csurf \u00e8 ancora utilizzabile nel 2026?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. <code>csurf<\/code> \u00e8 deprecato e abbandonato dal 2020. Non riceve correzioni di sicurezza e non gestisce correttamente i cookie <code>SameSite<\/code> moderni. Per ogni nuovo progetto usa <code>csrf-csrf<\/code> o la protezione nativa del tuo framework, come <code>@fastify\/csrf-protection<\/code> per Fastify.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"se-uso-samesitestrict-ho-ancora-bisogno-di-un-token-csrf\">Se uso SameSite=Strict, ho ancora bisogno di un token CSRF?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">S\u00ec. <code>SameSite<\/code> \u00e8 una difesa preziosa ma non sufficiente da sola: il comportamento varia tra browser, non copre tutti i casi e i browser pi\u00f9 datati lo gestiscono in modo incoerente. OWASP raccomanda di combinare il token CSRF con <code>SameSite<\/code>, non di sostituirlo. La difesa a strati \u00e8 la strategia corretta.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"le-api-rest-con-autenticazione-tramite-token-bearer-sono-vulnerabili-a-csrf\">Le API REST con autenticazione tramite token Bearer sono vulnerabili a CSRF?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Se l&#8217;autenticazione si basa esclusivamente su un token Bearer inviato in un header <code>Authorization<\/code> (e non su cookie), l&#8217;attacco CSRF classico non si applica, perch\u00e9 il browser non allega automaticamente quell&#8217;header. Il rischio CSRF nasce quando l&#8217;autenticazione viaggia nei cookie. Se la tua SPA usa cookie di sessione, la protezione CSRF resta necessaria.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"qual-e-la-differenza-tra-csrf-e-xss\">Qual \u00e8 la differenza tra CSRF e XSS?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Il CSRF induce il browser della vittima a inviare richieste indesiderate sfruttando la sessione esistente, senza leggere dati. L&#8217;XSS esegue codice malevolo nel contesto del sito, potendo leggere token, cookie e dati. Sono attacchi distinti ma collegati: un XSS pu\u00f2 aggirare la protezione CSRF leggendo il token, motivo per cui difendersi dall&#8217;XSS \u00e8 prioritario.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"devo-generare-un-token-csrf-nuovo-a-ogni-richiesta\">Devo generare un token CSRF nuovo a ogni richiesta?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Non \u00e8 obbligatorio. Un token per sessione, firmato HMAC come fa <code>csrf-csrf<\/code>, \u00e8 sicuro per la maggior parte delle applicazioni. Il token per richiesta aggiunge protezione contro il replay ed \u00e8 consigliato solo per le operazioni pi\u00f9 critiche, dove vale la complessit\u00e0 aggiuntiva di gestione.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"la-protezione-csrf-rallenta-lapplicazione\">La protezione CSRF rallenta l&#8217;applicazione?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;impatto \u00e8 trascurabile. La generazione e la verifica di un token HMAC con il modulo <code>crypto<\/code> nativo di Node.js richiedono microsecondi per richiesta, ben al di sotto della latenza di rete o di database. Il pattern Double Submit non richiede storage lato server, quindi non aggiunge carico al database.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"come-gestisco-il-token-csrf-in-una-spa-con-piu-schede-aperte\">Come gestisco il token CSRF in una SPA con pi\u00f9 schede aperte?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Con il pattern Double Submit legato al cookie, tutte le schede dello stesso dominio condividono il cookie del token, quindi funzionano in modo coerente. Recupera il token all&#8217;avvio di ogni istanza dell&#8217;app e, se una richiesta restituisce 403, richiedine uno nuovo dall&#8217;endpoint dedicato e ripeti l&#8217;operazione una sola volta.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"posso-usare-lo-stesso-backend-per-form-html-e-spa\">Posso usare lo stesso backend per form HTML e SPA?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">S\u00ec, ed \u00e8 esattamente ci\u00f2 che fa la configurazione di questo tutorial. La funzione <code>getTokenFromRequest<\/code> cerca il token prima nell&#8217;header <code>x-csrf-token<\/code> (SPA) e poi nel campo <code>_csrf<\/code> del body (form). Lo stesso endpoint protetto accetta entrambe le modalit\u00e0 senza modifiche.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"approfondimenti-correlati\">Approfondimenti correlati<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/it\/autenticazione-jwt-nodejs\/\">Autenticazione JWT in Node.js: 12 Step [2026]<\/a><\/li><li><a href=\"\/it\/hashing-password-bcrypt-nodejs\/\">Hashing Password con bcrypt in Node.js: 12 Step [2026]<\/a><\/li><li><a href=\"\/it\/crittografia-end-to-end-nodejs\/\">Crittografia End-to-End in Node.js: 12 Step [2026]<\/a><\/li><li><a href=\"\/it\/https-e-tls\/\">HTTPS e TLS: come viene protetta una connessione web<\/a><\/li><li><a href=\"\/it\/sicurezza-delle-password\/\">Sicurezza delle password: lunghezza, hashing e secondo fattore<\/a><\/li><li><a href=\"\/it\/certbot-lets-encrypt-https\/\">Let&#8217;s Encrypt e Certbot: HTTPS Gratis in 10 Step [2026]<\/a><\/li><li><a href=\"\/it\/security-hub\/\">Sicurezza online: la guida pratica<\/a><\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">La protezione CSRF non \u00e8 un componente opzionale da aggiungere alla fine: \u00e8 una parte fondamentale di ogni applicazione Node.js che gestisce sessioni basate su cookie. Con i 12 step di questo tutorial hai un&#8217;implementazione completa, testata e allineata alle raccomandazioni OWASP 2025, pronta per essere portata in produzione e adattata alle esigenze del tuo progetto.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Un attacco CSRF (Cross-Site Request Forgery) sfrutta la sessione attiva di un utente per eseguire azioni che l&#8217;utente non ha mai voluto: cambiare la password, trasferire denaro, eliminare un account.\u2026<\/p>\n","protected":false},"author":5,"featured_media":125,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-124","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/124","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/comments?post=124"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/124\/revisions"}],"predecessor-version":[{"id":126,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/124\/revisions\/126"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/media\/125"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/media?parent=124"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/categories?post=124"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/tags?post=124"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}