{"id":1081,"date":"2023-05-08T20:00:00","date_gmt":"2023-05-08T18:00:00","guid":{"rendered":"https:\/\/www.ilbytecidio.it\/?p=1081"},"modified":"2023-07-02T10:57:20","modified_gmt":"2023-07-02T08:57:20","slug":"un-sito-web-senza-cookie-si-puo-fare","status":"publish","type":"post","link":"https:\/\/www.ilbytecidio.it\/?p=1081","title":{"rendered":"Un sito web senza cookie? Si pu\u00f2 fare"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/www.ilbytecidio.it\/wp-content\/uploads\/2023\/05\/cookies-gb0798f051_1920.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"678\" src=\"https:\/\/www.ilbytecidio.it\/wp-content\/uploads\/2023\/05\/cookies-gb0798f051_1920-1024x678.jpg\" alt=\"\" class=\"wp-image-1088\" srcset=\"https:\/\/www.ilbytecidio.it\/wp-content\/uploads\/2023\/05\/cookies-gb0798f051_1920-1024x678.jpg 1024w, https:\/\/www.ilbytecidio.it\/wp-content\/uploads\/2023\/05\/cookies-gb0798f051_1920-300x199.jpg 300w, https:\/\/www.ilbytecidio.it\/wp-content\/uploads\/2023\/05\/cookies-gb0798f051_1920-768x509.jpg 768w, https:\/\/www.ilbytecidio.it\/wp-content\/uploads\/2023\/05\/cookies-gb0798f051_1920-1536x1018.jpg 1536w, https:\/\/www.ilbytecidio.it\/wp-content\/uploads\/2023\/05\/cookies-gb0798f051_1920-453x300.jpg 453w, https:\/\/www.ilbytecidio.it\/wp-content\/uploads\/2023\/05\/cookies-gb0798f051_1920.jpg 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Immagine di <a href=\"https:\/\/pixabay.com\/users\/congerdesign-509903\/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=547636\">congerdesign<\/a> da <a href=\"https:\/\/pixabay.com\/\/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=547636\">Pixabay<\/a><\/figcaption><\/figure>\n\n\n\n<p>Agli albori della mia carriera di programmatore web mi piaceva fare in modo che i miei siti funzionassero bene anche senza JavaScript e senza cookie. Capitava spesso infatti che alcuni utenti disattivassero entrambi per ragioni di sicurezza. Con l&#8217;arrivo della legge sui cookie, la mia premura \u00e8 tornata attuale. L&#8217;informativa obbligatoria su tutti i siti che visitiamo \u00e8 oltremodo fastidiosa e per qualcuno che non \u00e8 esperto di questioni legali c&#8217;\u00e8 sempre il rischio di non fare le cose completamente in regola. Vale dunque la pena di chiedersi se installare cookie nel browser dell&#8217;utente \u00e8 davvero necessario.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>Cominciamo col dire che, a parte gli strumenti di tracciamento come Google Analytics, l&#8217;uso principale dei cookie \u00e8 quello di mantenere una sessione (nel qual caso si parla di &#8220;cookie tecnici&#8221;), mentre la funzione di salvare preferenze dell&#8217;utente \u00e8 ormai trasferita quasi del tutto alla sessione stessa e all&#8217;archiviazione locale via Javascript. Premesso che si parla gi\u00e0 dell&#8217;eliminazione completa dei cookie in pochi anni e che esistono gi\u00e0 varie tecniche di tracciamento che ne fanno a meno, vorrei concentrarmi su come mantenere una sessione in sicurezza facendo a meno di questo collaudato strumento.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Applicazioni a pagina singola<\/h2>\n\n\n\n<p>Le applicazioni a singola pagina sono quei siti web in cui il browser carica inizialmente una sola pagina, il cui contenuto viene poi gestito interamente via JavaScript. Servizi che usiamo quotidianamente sono costruiti in questa maniera. Facebook \u00e8 un esempio chiaro, ma sono cos\u00ec anche il motore di ricerca di Google e molti altri. Svariati strumenti di sviluppo consentono di creare un sito web a pagina singola, come ad esempio i framework AngularJs e React, ma ci si pu\u00f2 arrangiare anche con la coppia Laravel + Livewire e con molto altro.<br>Il buono di questa tecnica \u00e8 che, non dovendo caricare pi\u00f9 pagine, non dobbiamo preoccuparci di recuperare la sessione di lavoro. Possiamo infatti lavorare alla nostra applicazione esattamente come se si trattasse di un servizio web (<em>web service<\/em>), disaccoppiando completamente il lato server ed il lato client. Un buon servizio web \u00e8 senza stato, come prevede il protocollo HTTP, e dovr\u00e0 occuparsi di verificare le credenziali dell&#8217;utente a ogni richiesta. Non ci sar\u00e0 quindi una sessione, ma solo <em>risorse <\/em>su cui l&#8217;utente svolger\u00e0 operazioni attraverso le varie richieste (si veda il concetto di server <em>RESTful<\/em>).<br>Tipicamente, avremo un server di autenticazione che si occuper\u00e0 di creare un <em>token<\/em>. Quest&#8217;ultimo sar\u00e0 salvato localmente (in una semplice variabile Javascript o nell&#8217;archiviazione del browser) ed incluso nell&#8217;intestazione (<em>header<\/em>) di ogni richiesta al servizio web, che lo utilizzer\u00e0 per identificare l&#8217;utente.<br>Non mi dilungher\u00f2 n\u00e9 sui concetti di autenticazione ed autorizzazione, n\u00e9 su come implementarli in concreto. Ci sono svariati strumenti e protocolli che si possono utilizzare e la scelta dipende da molti fattori, come il tipo di applicazione da sviluppare (un&#8217;app per una banca o per un servizio medico avr\u00e0 un livello di sicurezza molto pi\u00f9 alto dell&#8217;accesso all&#8217;area commenti di un blog, per esempio). <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Applicazioni a pagina multipla<\/h2>\n\n\n\n<p>In questo caso ci troviamo di fronte ad un sito web tradizionale, composto da varie pagine, con uno stato salvato nella sessione. Nei miei esempi far\u00f2 riferimento a PHP come linguaggio lato-server, ma gli stessi concetti valgono qualsiasi linguaggio utilizziamo.<br>Normalmente la sessione viene mantenuta salvando un ID nei cookie. Il browser invia i cookie con ogni richiesta, quindi il programmatore pu\u00f2 semplicemente chiamare <em>session_start<\/em> ad ogni esecuzione dello script ed i dati salvati saranno l\u00ec. Si pu\u00f2 fare a meno di salvare l&#8217;ID della sessione come cookie, a patto di riuscire ad inviarlo in sicurezza in un&#8217;altra maniera.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Parametro GET<\/h3>\n\n\n\n<p>Il metodo pi\u00f9 semplice per inviare l&#8217;ID della sessione senza cookie \u00e8 quello di aggiungerlo alla <em>query string<\/em> di ogni richiesta, che quindi apparir\u00e0 come www.miosito.com\/pagina?PHPSESSID=mio-id-sessione<br>L&#8217;ID stesso \u00e8 recuperabile (ed impostabile) chiamando la funzione <em>session_id<\/em>. PHP estrarr\u00e0 l&#8217;ID dalla richiesta e tutto funzioner\u00e0 esattamente come con i cookie attivati. I pi\u00f9 attenti avranno gi\u00e0 notato uno scenario problematico nell&#8217;utilizzo di questa tecnica. Immaginiamo che un utente acceda alla pagina e la trovi molto interessante. A questo punto far\u00e0 una copia dell&#8217;URL e lo invier\u00e0 ad un amico o, peggio, lo condivider\u00e0 su un <em>social<\/em>. Chiunque aprir\u00e0 il collegamento utilizzer\u00e0 la stessa sessione del primo utente. Se la sessione contiene solo impostazioni di secondaria importanza non sar\u00e0 un grosso problema, ma se vi sono contenuti dati personali e\/o l&#8217;ID dell&#8217;utente che permette l&#8217;accesso ad aree private, questo \u00e8 inaccettabile. Potremmo adottare qualche accorgimento, come una verifica su alcuni dati dell&#8217;utente (indirizzo IP, user agent ed altri dati) ma nulla di ci\u00f2 che troviamo in una normale richiesta \u00e8 veramente univoco: due persone connesse con la stessa versione del browser alla stessa rete potrebbero essere facilmente confuse.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Parametro POST<\/h3>\n\n\n\n<p>Per evitare di condividere la sessione insieme a tutti i collegamenti possiamo usare un approccio differente, quello di usare richieste di tipo POST, le stesse dei moduli (<em>form<\/em>), ed inserire l&#8217;ID della sessione come campo nascosto. Normalmente non si pu\u00f2 usare POST per un semplice collegamento ad un&#8217;altra pagina, ma con CSS possiamo rendere un pulsante d&#8217;invio (<em>submit<\/em>) in tutto e per tutto simile ad un tag &lt;a&gt;. Per comodit\u00e0, possiamo usare una libreria che offra questa possibilit\u00e0. Con <a rel=\"noreferrer noopener\" href=\"https:\/\/getbootstrap.com\/\" target=\"_blank\">bootstrap<\/a> basta aggiungere al pulsante le classi &#8220;btn btn-link&#8221;. In questa maniera, ogni link condiviso porter\u00e0 ad una pagina con una sessione pulita. Bench\u00e9 migliore della soluzione con parametro GET, anche questa ha diversi problemi:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>in primis, \u00e8 un utilizzo improprio della richiesta di tipo POST. Non vi \u00e8 accordo se POST debba essere usato per creare nuove risorse o modificarle, ma per una semplice visualizzazione, \u00e8 GET il tipo designato. Poco male, comunque. \u00c8 un buon prezzo per una navigazione in tranquillit\u00e0, senza cookie e senza fastidiosi popup;<\/li>\n\n\n\n<li>l&#8217;utente stesso a cui la sessione \u00e8 associata rientrando trover\u00e0 una sessione pulita, senza il suo eventuale accesso al suo profilo, esattamente come se avesse cancellato i cookie con l&#8217;approccio classico. Se questo \u00e8 pi\u00f9 che desiderabile per un conto bancario, pu\u00f2 essere seccante per qualsiasi altro utilizzo meno critico;<\/li>\n\n\n\n<li>l&#8217;ID della sessione compare nel codice sorgente della pagina. Questo significa che se salviamo la pagina come file HTML e lo condividiamo con qualcuno, il destinatario avr\u00e0 accesso alla nostra sessione con un semplice click su un collegamento, tornando in parte allo stesso problema dei parametri GET. <\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Parametro POST via JavaScript<\/h3>\n\n\n\n<p>Possiamo lasciar correre il problema n.1 del punto precedente perch\u00e9 esclusivamente formale, mentre gli altri due sono pi\u00f9 importanti per l&#8217;utente medio. Possiamo risolverli con l&#8217;utilizzo di JavaScript, che ormai tutti mantengono attivo sul browser, dal momento che \u00e8 alla base di quasi tutte le piattaforme d&#8217;uso comune.<br>Invece di passare la sessione insieme al codice HTML, creiamo un&#8217;API che ne avvii una nuova e restituisca l&#8217;ID. Il nostro codice JavaScript utilizzer\u00e0 quest&#8217;API per salvare l&#8217;ID nell&#8217;archiviazione locale del browser e poi ad ogni click su uno dei collegamenti imposter\u00e0 l&#8217;apposito campo del modulo. Se l&#8217;ID \u00e8 gi\u00e0 presente in locale, verr\u00e0 utilizzato per ripristinare la sessione. In questo modo, a patto di ricevere via AJAX eventuali informazioni del profilo da mostrare nella pagina (status di accesso, nome utente, ecc), l&#8217;utente ritrover\u00e0 la sua sessione senza dover accedere di nuovo ed essendo l&#8217;ID aggiunto &#8220;al volo&#8221; al modulo da inviare, non comparir\u00e0 nel codice sorgente.<br>Il codice che segue rappresenta questa soluzione, in una versione essenziale. Per semplicit\u00e0, tutto si trova in un&#8217;unico file, ma voi ricordate di creare sempre file separati per PHP, template HTML, CSS e JavaScript.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>&lt;?php\n\n\/* Accetta PHPSESSID da parametri GET e POST \n   Se il server non lo permette, occorrer\u00e0 passare manualmente il valore di PHPSESSID a session_id() *\/\nini_set('session.use_only_cookies', false);\n\/\/ Non creare il cookie PHPSESSID\nini_set('session.use_cookies', false);\n\nif (isset($_POST&#91;'PHPSESSID']) || isset($_POST&#91;'get_session_id'])) {\n  session_start();\n}\n\nif (isset($_POST&#91;'get_session_id'])) {\n  echo session_id();\n  exit();\n}\n?&gt;\n\n&lt;!doctype html&gt;\n&lt;html lang=\"it\"&gt;\n  &lt;head&gt;\n    &lt;title&gt;Il mio sito senza cookie&lt;\/title&gt;\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"&gt;\n    &lt;link rel=\"stylesheet\" href=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.0-alpha3\/dist\/css\/bootstrap.min.css\"&gt;\n  &lt;\/head&gt;\n  &lt;body&gt;\n    &lt;form action=\"index.php?pagina=link1\" method=\"post\"&gt;\n      &lt;input type=\"hidden\" name=\"PHPSESSID\" value=\"\"&gt;\n      &lt;input type=\"submit\" class=\"btn btn-link\" value=\"Link 1\"&gt;\n    &lt;\/form&gt;\n    &lt;form action=\"index.php?pagina=link2\" method=\"post\"&gt;\n      &lt;input type=\"hidden\" name=\"PHPSESSID\" value=\"\"&gt;\n      &lt;input type=\"submit\" class=\"btn btn-link\" value=\"Link 2\"&gt;\n    &lt;\/form&gt;\n    \n    &lt;p&gt;\n      Valore di session_id: &lt;span&gt;&lt;?php echo session_id() ?: 'NON PRESENTE' ?&gt;&lt;\/span&gt;&lt;br&gt;\n      Sessione salvata nel browser: &lt;span id=\"session_js\"&gt;&lt;\/span&gt;&lt;br&gt;\n      Se i due valori saranno uguali visitando i vari link, tutto funziona correttamente.\n    &lt;\/p&gt;\n\n    &lt;script src=\"https:\/\/code.jquery.com\/jquery-3.6.4.min.js\"&gt;&lt;\/script&gt;\n    &lt;script src=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.0-alpha3\/dist\/js\/bootstrap.bundle.min.js\"&gt;&lt;\/script&gt;\n    &lt;script&gt;\n      $(document).ready(function () {\n        \/\/ Cerca l'ID di sessione nell'archiviazione del browser\n        let session_id = window.localStorage.getItem('user_session');\n        if (session_id === null) {\n          \/\/ L'ID non \u00e8 salvato. Ne viene richiesto uno nuovo\n          $.post(\"index.php\", {get_session_id: 1})\n          .done(function(data) {\n            window.localStorage.setItem('user_session', data);\n            $('#session_js').html(data);\n          })\n          .fail(function() {\n            alert(\"Impossibile creare una sessione\");\n          });\n        } else {\n          $('#session_js').html(session_id);\n        }\n\n        $('.btn-link').click(function (e) {\n          \/\/ Click su uno dei collegamenti. Aggiungi l'ID di sessione ed invia.\n          e.preventDefault();\n          const $form = $(this).parent();\n          $form.children('input&#91;name=\"PHPSESSID\"]').val(window.localStorage.getItem('user_session'));\n          $form.submit();\n        });\n      });\n    &lt;\/script&gt;\n  &lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Considerazioni sulla sicurezza<\/h3>\n\n\n\n<p>Salvare l&#8217;ID della sessione nell&#8217;archiviazione del browser non \u00e8 meno sicuro di usare un cookie. Forse nemmeno di pi\u00f9, considerato che ci sono svariate tecniche di hackeraggio a cui dobbiamo prestare attenzione. L&#8217;archiviazione locale usa dei file nel dispositivo dell&#8217;utente, esattamente come per i cookie, e ne restringe l&#8217;accesso a pagine provenienti dalla stessa <a rel=\"noreferrer noopener\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Glossary\/Origin\" data-type=\"URL\" data-id=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Glossary\/Origin\" target=\"_blank\">origine<\/a>. Qualsiasi utente collegato alla rete deve prendere tutte le precauzioni possibili per evitare che malware attacchino il dispositivo e ne rubino dati importanti, codici di sessione inclusi.<br>I cookie avevano fino a qualche anno fa il grosso problema di essere inviati insieme a tutte le richieste al server, comprese quelle non sicure, e quindi di essere leggibili da chiunque riuscisse ad intercettare il traffico sulla rete. I dati salvati a livello locale vengono inviati al server solo quando ce n&#8217;\u00e8 effettivamente bisogno, dando pi\u00f9 controllo al programmatore sul loro traffico.  Ad ogni modo, con l&#8217;uso sempre pi\u00f9 massiccio di HTTPS dobbiamo preoccuparci di meno che vengano rubati per strada mentre, come abbiamo visto, \u00e8 ancora importante evitare che siano esposto quando questo risulta non necessario o addirittura dannoso.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusioni<\/h2>\n\n\n\n<p>I cookie sono stati uno strumento importante per mantenere lo stato fra una richiesta e l&#8217;altra al server web attraverso un protocollo, HTTP, che di per s\u00e9 \u00e8 senza stato. Nella loro semplicit\u00e0 si sono rivelati comunque uno strumento potente e spesso utilizzato per fini non proprio cristallini, come quello di seguire passo-passo l&#8217;attivit\u00e0 dell&#8217;utente al fine di registrarne l&#8217;attivit\u00e0, le abitudini ed a volte persino dati sensibili. L&#8217;Unione Europea \u00e8 corsa ai ripari per garantire la nostra riservatezza, ma non si pu\u00f2 dire che abbia scelto il modo migliore. Sebbene sia sacrosanto informare gli utenti dell&#8217;uso che si vuol fare dei loro dati e chiederne il consenso, ci siamo ritrovati con fastidiosi pop-up ad ogni navigazione, fatto ancora pi\u00f9 irritante per chi sceglie di cancellare automaticamente i dati di navigazione all&#8217;uscita del browser. Peraltro, non \u00e8 detto che siti malevoli rispettino davvero le nostre scelte, quindi rimane importante che l&#8217;utente presti attenzione a ci\u00f2 che viene salvato nel proprio dispositivo. Sarebbe stato forse meglio tipizzare i cookie e dare all&#8217;utente la maniera di configurare una volta per tutte il comportamento del browser in presenza dell&#8217;uno o l&#8217;altro tipo e permettendo di creare eccezioni in senso pi\u00f9 permissivo o pi\u00f9 restrittivo rispetto all&#8217;impostazione generale. Si avrebbe avuto un controllo pi\u00f9 standardizzato ed una navigazione pi\u00f9 agevole, ma sembra che ormai la direzione sia quella di abbandonare del tutto i cookie, anche se, come gi\u00e0 accennato, ci sono tecniche di tracciamento che funzionano altrettanto bene ed in modo pi\u00f9 subdolo.<br>Se si giungesse ad un web senza cookie sarebbe auspicabile avere un campo standard per l&#8217;ID della sessione nella prossima versione di HTTP, ma come abbiamo visto in quest&#8217;articolo \u00e8 gi\u00e0 possibile farne a meno sfruttando ci\u00f2 che abbiamo gi\u00e0 a disposizione.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Agli albori della mia carriera di programmatore web mi piaceva fare in modo che i miei siti funzionassero bene anche senza JavaScript e senza cookie. Capitava spesso infatti che alcuni utenti disattivassero entrambi per ragioni di sicurezza. Con l&#8217;arrivo della &hellip; <a href=\"https:\/\/www.ilbytecidio.it\/?p=1081\">Continua a leggere<span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":1088,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[19],"tags":[65,115,116,62,117,76],"class_list":["post-1081","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-programmazione-web","tag-cookie","tag-javascript","tag-php","tag-riservatezza","tag-sessione","tag-sicurezza"],"views":273,"_links":{"self":[{"href":"https:\/\/www.ilbytecidio.it\/index.php?rest_route=\/wp\/v2\/posts\/1081","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ilbytecidio.it\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ilbytecidio.it\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ilbytecidio.it\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ilbytecidio.it\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1081"}],"version-history":[{"count":9,"href":"https:\/\/www.ilbytecidio.it\/index.php?rest_route=\/wp\/v2\/posts\/1081\/revisions"}],"predecessor-version":[{"id":1097,"href":"https:\/\/www.ilbytecidio.it\/index.php?rest_route=\/wp\/v2\/posts\/1081\/revisions\/1097"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.ilbytecidio.it\/index.php?rest_route=\/wp\/v2\/media\/1088"}],"wp:attachment":[{"href":"https:\/\/www.ilbytecidio.it\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1081"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ilbytecidio.it\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1081"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ilbytecidio.it\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1081"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}