`Cookie` in TypeScript

`Cookie` in TypeScript

Questo articolo spiega Cookie in TypeScript.

Esaminiamo schemi pratici per gestire i cookie in modo sicuro e affidabile sia nel browser sia sul server.

YouTube Video

Cookie in TypeScript

Concetti di base dei cookie

Un cookie è un meccanismo per archiviare sul client piccole stringhe (coppie nome=valore), creato tramite l'intestazione HTTP Set-Cookie o document.cookie. Puoi controllarne il comportamento con attributi di sicurezza (HttpOnly, Secure, SameSite, ecc.).

Operazioni di base nel browser: document.cookie

Di seguito l'esempio minimo per scrivere un cookie nel browser. Crea un cookie aggiungendo una stringa a document.cookie.

 1// Set a simple cookie that expires in 7 days.
 2// Note: Comments are in English per the user's preference.
 3const setSimpleCookie = (name: string, value: string) => {
 4  const days = 7;
 5  const expires = new Date(Date.now() + days * 86400_000).toUTCString();
 6  // name=value; Expires=...; Path=/
 7  document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; Expires=${expires}; Path=/`;
 8};
 9
10setSimpleCookie('theme', 'dark');
  • Questo codice crea un cookie che scade tra 7 giorni. È facile da usare lato browser, ma poiché non puoi impostare HttpOnly, dovresti evitare di memorizzare informazioni sensibili.

Lettura dei cookie (browser)

La seguente funzione è un helper che recupera il valore del cookie con un nome specificato da document.cookie. document.cookie viene restituito come una stringa delimitata da un punto e virgola e uno spazio ('; '). Pertanto lo suddividiamo e cerchiamo il cookie desiderato.

 1// Parse document.cookie and return value for given name or null if not found.
 2const getCookie = (name: string): string | null => {
 3  const cookies = document.cookie ? document.cookie.split('; ') : [];
 4  for (const cookie of cookies) {
 5    const [k, ...rest] = cookie.split('=');
 6    const v = rest.join('=');
 7    if (decodeURIComponent(k) === name) {
 8      return decodeURIComponent(v);
 9    }
10  }
11  return null;
12};
13
14// Example usage:
15const theme = getCookie('theme'); // => "dark" if set
16console.log('theme cookie:', theme);
  • Questa funzione decodifica sia il nome sia il valore del cookie per confrontarli e recuperarli in modo sicuro. Gestisce nomi di cookie duplicati e caratteri speciali codificati, rendendola un'implementazione semplice ma robusta.

Comprendere e applicare gli attributi di sicurezza

I cookie hanno diversi attributi importanti, ciascuno dei quali controlla la sicurezza e l'ambito del loro comportamento.

  • L'attributo HttpOnly impedisce che il cookie sia accessibile da JavaScript, contribuendo a mitigare gli attacchi XSS (cross-site scripting). Nota che non puoi impostare HttpOnly dal lato browser.
  • L'attributo Secure limita l'invio dei cookie solo tramite HTTPS, riducendo il rischio di intercettazioni e manomissioni.
  • L'attributo SameSite controlla se i cookie vengono inviati con richieste cross-site e aiuta a prevenire gli attacchi CSRF (cross-site request forgery).
  • L'attributo Path specifica l'ambito dei percorsi di richiesta per i quali il cookie viene inviato, e l'attributo Domain specifica il dominio in cui il cookie è valido.
  • Impostando l'attributo Expires o Max-Age, puoi controllare la scadenza del cookie.

Esempio di aggiunta di SameSite / Secure nel browser (ciò che è possibile)

HttpOnly non può essere impostato lato client. Di seguito un esempio di aggiunta di SameSite e Secure lato client. Tuttavia, Secure ha effetto solo sulle pagine HTTPS.

 1// Set cookie with SameSite and Secure attributes (Secure only effective over HTTPS).
 2const setCookieWithAttributes = (name: string, value: string) => {
 3  const maxAge = 60 * 60 * 24 * 7; // 7 days in seconds
 4  // Note: HttpOnly cannot be set from JS; set it on server-side when you want to restrict JS access.
 5  document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; `
 6    + `Max-Age=${maxAge}; `
 7    + `Path=/; `
 8    + `SameSite=Lax; `
 9    + `Secure`;
10};
11
12setCookieWithAttributes('session_hint', 'true');
  • SameSite=Lax è una scelta sicura e comune. Per restrizioni più rigide usa SameSite=Strict, ma navigazioni legittime da siti esterni potrebbero smettere di funzionare.

Impostare i cookie sul server (Set-Cookie) (Node / TypeScript)

Sul server puoi aggiungere HttpOnly tramite l'intestazione Set-Cookie, perciò la gestione delle sessioni idealmente dovrebbe avvenire lato server. Di seguito un esempio che usa il modulo http di Node.

 1// A minimal Node HTTP server in TypeScript that sets a secure HttpOnly cookie.
 2// This example uses built-in 'crypto' for a random session id.
 3import http from 'http';
 4import crypto from 'crypto';
 5
 6const server = http.createServer((req, res) => {
 7  if (req.url === '/login') {
 8    const sessionId = crypto.randomBytes(16).toString('hex');
 9    // Set cookie with HttpOnly, Secure, SameSite and Path
10    // Expires is optional — Max-Age preferred for relative lifetimes.
11    res.setHeader('Set-Cookie', `sid=${sessionId}; `
12      + `HttpOnly; `
13      + `Secure; `
14      + `SameSite=Strict; `
15      + `Path=/; `
16      + `Max-Age=3600`
17    );
18    res.writeHead(302, { Location: '/' });
19    res.end();
20    return;
21  }
22
23  res.writeHead(200, { 'Content-Type': 'text/plain' });
24  res.end('Hello\n');
25});
26
27server.listen(3000, () => {
28  console.log('Server running on http://localhost:3000');
29});
  • Quando imposti l'attributo HttpOnly lato server, il cookie diventa inaccessibile da JavaScript, rendendo più difficile per gli attacchi XSS (cross-site scripting) rubarlo. Inoltre, aggiungendo l'attributo Secure in modo che i cookie vengano inviati sempre tramite HTTPS, puoi prevenire intercettazioni e manomissioni e migliorare la sicurezza delle comunicazioni.

Implementazione di serializzazione/parsing dei cookie (un helper lato server)

Di seguito una semplice utility lato server che costruisce una stringa di intestazione Set-Cookie.

 1// Cookie serialization helper for server-side use.
 2// Returns a properly formatted Set-Cookie header value.
 3type CookieOptions = {
 4  path?: string;
 5  domain?: string;
 6  maxAge?: number;
 7  expires?: Date;
 8  httpOnly?: boolean;
 9  secure?: boolean;
10  sameSite?: 'Strict' | 'Lax' | 'None';
11};
12
13const serializeCookie = (name: string, value: string, opts: CookieOptions = {}): string => {
14  const parts: string[] = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
15
16  if (opts.maxAge != null) parts.push(`Max-Age=${Math.floor(opts.maxAge)}`);
17  if (opts.expires) parts.push(`Expires=${opts.expires.toUTCString()}`);
18  if (opts.domain) parts.push(`Domain=${opts.domain}`);
19  parts.push(`Path=${opts.path ?? '/'}`);
20  if (opts.httpOnly) parts.push('HttpOnly');
21  if (opts.secure) parts.push('Secure');
22  if (opts.sameSite) parts.push(`SameSite=${opts.sameSite}`);
23
24  return parts.join('; ');
25};
26
27// Example usage:
28const headerValue = serializeCookie('uid', 'abc123', {
29  httpOnly: true,
30  secure: true,
31  sameSite: 'Lax',
32  maxAge: 3600
33});
34console.log(headerValue);
35// => "uid=abc123; Max-Age=3600; Path=/; HttpOnly; Secure; SameSite=Lax"
  • Questa utilità costituisce la base per creare Set-Cookie su un server personalizzato. Le librerie possono gestire molti più casi limite.

Prevenire manomissioni con firme (HMAC)

È pericoloso memorizzare direttamente nei cookie valori importanti. Introduciamo un metodo che firma i valori sul server per rilevare manomissioni. Qui usiamo HMAC-SHA256. In produzione, è inoltre necessario gestire la revoca dei token sul lato server.

 1// A simple HMAC signing and verification helper for cookies.
 2// Signing prevents client from tampering cookie values.
 3import crypto from 'crypto';
 4
 5const SECRET = 'replace_with_env_secret'; // store in env var in production
 6
 7const signValue = (value: string) => {
 8  const hmac = crypto.createHmac('sha256', SECRET);
 9  hmac.update(value);
10  return `${value}.${hmac.digest('hex')}`;
11};
12
13const verifySignedValue = (signed: string): string | null => {
14  const idx = signed.lastIndexOf('.');
15  if (idx === -1) return null;
16  const value = signed.slice(0, idx);
17  const sig = signed.slice(idx + 1);
18
19  const hmac = crypto.createHmac('sha256', SECRET);
20  hmac.update(value);
21  const expected = hmac.digest('hex');
22  // use timing-safe comparison in production
23  if (crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
24    return value;
25  }
26  return null;
27};
28
29// Example usage:
30const signed = signValue('userId=42');
31console.log('signed:', signed);
32console.log('verified:', verifySignedValue(signed)); // => "userId=42"
  • Con questo metodo puoi verificare se il valore del cookie è stato manomesso. Non includere la chiave di firma nel codice sorgente; gestiscila tramite mezzi sicuri, come le variabili d’ambiente.

Cookie e mitigazione CSRF (schemi generali)

Le sessioni che usano i cookie necessitano di protezione CSRF. Le mitigazioni tipiche includono:.

  • Imposta SameSite su Lax o Strict per evitare invii cross-site non necessari. Utilizza SameSite=None; Secure solo quando la comunicazione tra origini diverse è necessaria.
  • Utilizza token CSRF, validati dal server, per i moduli e le richieste API. Non memorizzare il token in un cookie HttpOnly; invece, forniscilo tramite il corpo della risposta o i meta tag e fai sì che JavaScript lo legga solo quando necessario.
  • Se il token è accessibile da JavaScript, assicurati di implementare anche le protezioni contro l'XSS (CSP, escaping dell'output, ecc.).
  • Richiedi la riautenticazione per le azioni sensibili e al login, e previeni gli attacchi di fissazione della sessione.

Di seguito un esempio sicuro di client che invia il token CSRF in un'intestazione. Si presume che il client abbia già ricevuto il token dal server, ad esempio nel corpo della risposta.

 1// Example of sending CSRF token safely in a header using fetch.
 2// Assumes CSRF token was provided securely (e.g., via response body or meta tag).
 3async function sendProtectedRequest(url: string, csrfToken: string) {
 4  const res = await fetch(url, {
 5    method: 'POST',
 6    credentials: 'same-origin', // include cookies for same-site requests
 7    headers: {
 8      'Content-Type': 'application/json',
 9      'X-CSRF-Token': csrfToken
10    },
11    body: JSON.stringify({ action: 'doSomething' })
12  });
13  return res;
14}
  • Se specifichi credentials: 'same-origin', vengono inviati solo i cookie per la stessa origine. Sul server, convalida il valore dell'intestazione X-CSRF-Token e verifica che il token corrisponda. Se le richieste cross-site sono necessarie, configura CORS con attenzione.

Cross-site (CORS) e relazione con credentials

Per inviare e ricevere cookie nella comunicazione cross-origin, il client deve specificare credentials: 'include' e il server deve impostare Access-Control-Allow-Credentials: true. Tuttavia, limita questa configurazione alle origini attendibili e non utilizzare Access-Control-Allow-Origin: *.

 1async function fetchCrossOriginData() {
 2  // Example: cross-origin fetch sending cookies.
 3  // Server must set Access-Control-Allow-Credentials: true
 4  // and a specific trusted origin.
 5  const res = await fetch('https://api.example.com/data', {
 6    credentials: 'include', // send cookies only to trusted domains
 7    method: 'GET'
 8  });
 9  return res;
10}
  • La combinazione di CORS e cookie è molto delicata dal punto di vista della sicurezza. Gestisci rigorosamente le origini consentite con una whitelist ed evita la comunicazione cross-site non necessaria. Inoltre, quando utilizzi SameSite=None; Secure, imponi l'uso di HTTPS per prevenire attacchi man-in-the-middle.

Esempio: impostazioni sicure dei cookie di sessione con Express (TypeScript)

In pratica, usa Express, la libreria cookie, express-session, e così via. Di seguito un semplice esempio con express e cookie-parser. In pratica, imposta secure su true e gestisci il secret tramite variabili d'ambiente.

 1// Express example using cookie-parser and setting a secure httpOnly cookie.
 2// npm install express cookie-parser @types/express @types/cookie-parser
 3import express from 'express';
 4import cookieParser from 'cookie-parser';
 5
 6const app = express();
 7app.use(cookieParser(process.env.COOKIE_SECRET));
 8
 9app.post('/login', (req, res) => {
10  // authenticate user (omitted)
11  const sessionId = 'generated-session-id';
12  res.cookie('sid', sessionId, {
13    httpOnly: true,
14    secure: true,        // require HTTPS in production
15    sameSite: 'lax',
16    maxAge: 1000 * 60 * 60 // 1 hour
17  });
18  res.json({ ok: true });
19});
20
21app.listen(3000);
  • Utilizzando la funzionalità secret di cookieParser, puoi lavorare facilmente con cookie firmati. Tuttavia, nelle applicazioni reali, dal punto di vista della sicurezza e della scalabilità, non dovresti archiviare i dati direttamente nei cookie; invece, usa un archivio di sessione dedicato.

Prefissi dei cookie __Host- e __Secure-

I browser applicano regole speciali per alcuni prefissi.

  • Il prefisso __Secure- Se il nome di un cookie inizia con __Secure-, l'attributo Secure è obbligatorio.
  • Il prefisso __Host- Se inizia con __Host-, Secure è obbligatorio, il percorso deve essere / (root) e Domain non deve essere impostato.

Usarli riduce gli errori di configurazione e migliora la sicurezza.

Buone pratiche per i cookie

Per gestire i cookie in modo sicuro, considera i seguenti punti.

  • Non memorizzare direttamente nei cookie informazioni sensibili. Preferisci un ID di sessione lato server per i token di accesso.
  • Imposta i cookie di sessione con HttpOnly, Secure e SameSite=Lax (o Strict).
  • Sfrutta prefissi come __Host- e __Secure-.
  • Prendi in considerazione firme (HMAC) e crittografia per prevenire manomissioni e intercettazioni.
  • Abilita Secure e richiedi HTTPS.
  • Punta al principio del minimo privilegio e a scadenze brevi.
  • Usa token CSRF.
  • Attenzione alle differenze di comportamento di SameSite tra i browser, soprattutto quelli più vecchi.

Idee sbagliate comuni sui cookie

Per quanto riguarda i cookie, presta attenzione ai seguenti malintesi comuni.

  • 'Aggiungere HttpOnly elimina l'impatto dell'XSS.' Sebbene HttpOnly impedisca l'accesso ai cookie da parte di JavaScript, l'XSS può comunque essere sfruttato per inviare richieste arbitrarie. Dovresti anche adottare la verifica del token CSRF, la CSP (Content Security Policy) e la sanitizzazione dell'input.
  • 'Secure non è necessario per lo sviluppo in locale.' Simulare l'HTTPS e verificare il comportamento anche in ambienti locali migliora l'accuratezza dei test. Come minimo, HTTPS dovrebbe essere utilizzato negli ambienti di staging e produzione.
  • 'Periodi di scadenza lunghi sono comodi.' Se imposti durate dei cookie molto lunghe, aumenta il periodo durante il quale possono essere abusati se vengono rubati. Puoi impostare scadenze più brevi e prevedere ri-autenticazione periodica e rotazione dei token.

Riepilogo

Sebbene i cookie siano facili da usare, un uso improprio può introdurre vulnerabilità di sicurezza. Per gestirli correttamente con TypeScript, è importante comprendere attributi come HttpOnly, Secure e SameSite, e applicare impostazioni sicure lato server. Non memorizzando direttamente dati sensibili e combinando firme con scadenze brevi, puoi ottenere una gestione delle sessioni sicura e affidabile.

Puoi seguire l'articolo sopra utilizzando Visual Studio Code sul nostro canale YouTube. Controlla anche il nostro canale YouTube.

YouTube Video