Fetch API e cookie di sessione: inviare credenziali in modo sicuro

by Anastasia P.
SHARE
Fetch API e cookie di sessione: inviare credenziali in modo sicuro
© Guida-HTML5.it

Introduzione

Quando un’applicazione JavaScript comunica con un backend, spesso deve mantenere una sessione utente. Un modo comune per farlo consiste nell’utilizzare cookie HTTP, per esempio un cookie di sessione creato dopo il login. La Fetch API può inviare automaticamente questi cookie, ma il comportamento dipende dall’origine della richiesta e dall’opzione credentials.

Questo argomento è diverso dall’autenticazione tramite Bearer Token: invece di aggiungere manualmente un token nell’header Authorization, il browser gestisce i cookie secondo le regole di sicurezza HTTP. È una soluzione frequente nelle applicazioni web tradizionali e nelle architetture frontend-backend che usano sessioni.

Per esempio, se la pagina viene servita da https://app.example.com e deve chiamare un’API su https://api.example.com, il cookie potrebbe non essere inviato automaticamente. In questi casi bisogna configurare esplicitamente credentials: "include" e il server deve consentire le credenziali tramite CORS.

Codice completo

Il seguente esempio mostra una funzione riutilizzabile per recuperare il profilo dell’utente autenticato tramite cookie di sessione.

/**
 * Recupera il profilo dell´utente autenticato.
 *
 * @returns {Promise<Object>} Dati del profilo ricevuti dal server.
 */
async function getCurrentUser() {
  const response = await fetch("https://api.example.com/me", {
    method: "GET",

    // Invia i cookie anche quando la richiesta è cross-origin.
    credentials: "include",

    // Indica al server quale formato ci aspettiamo.
    headers: {
      Accept: "application/json"
    }
  });

  // fetch() non considera automaticamente 401 o 500 come eccezioni.
  if (!response.ok) {
    if (response.status === 401) {
      throw new Error("Utente non autenticato");
    }

    throw new Error(`Errore HTTP: ${response.status}`);
  }

  return response.json();
}

/**
 * Aggiorna l´interfaccia in base al risultato della richiesta.
 */
async function showProfile() {
  const output = document.querySelector("#profile");

  try {
    output.textContent = "Caricamento profilo...";

    const user = await getCurrentUser();

    output.textContent = `Benvenuto ${user.name}`;
  } catch (error) {
    console.error("Impossibile recuperare il profilo:", error);

    if (error.message === "Utente non autenticato") {
      output.textContent = "Esegui il login per continuare.";
      return;
    }

    output.textContent = "Si è verificato un problema temporaneo.";
  }
}

showProfile();

Per un backend sulla stessa origine, spesso è sufficiente usare il comportamento predefinito:

const response = await fetch("/api/me", {
  credentials: "same-origin"
});

Le tre modalità principali di credentials sono:

  • omit: non invia cookie né credenziali;
  • same-origin: invia i cookie solo per richieste sulla stessa origine; è il valore predefinito;
  • include: invia i cookie anche nelle richieste cross-origin, se il server lo consente.

Spiegazione

Origine, sito e cookie

Un’origine è identificata da protocollo, dominio e porta. Per esempio, https://app.example.com e https://api.example.com hanno host diversi e quindi sono origini diverse, anche se appartengono allo stesso sito principale.

Quando una richiesta è cross-origin, il browser applica le regole CORS. Il server deve rispondere con header compatibili, tra cui:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true

Con le credenziali abilitate, il valore di Access-Control-Allow-Origin non può essere *. Il server deve indicare esplicitamente l’origine autorizzata.

Impostare cookie sicuri

La sicurezza non dipende soltanto dal codice JavaScript. Il backend dovrebbe creare cookie con attributi appropriati:

Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure; SameSite=Lax
  • HttpOnly: impedisce a JavaScript di leggere il cookie tramite document.cookie, riducendo l’impatto di alcuni attacchi XSS;
  • Secure: invia il cookie soltanto tramite HTTPS;
  • SameSite: limita l’invio del cookie in richieste provenienti da altri siti;
  • Path: stabilisce per quali percorsi il cookie è valido.

Se il cookie deve essere inviato in contesti cross-site, potrebbe essere necessario SameSite=None; Secure. Questa configurazione va usata con attenzione perché aumenta la superficie esposta agli attacchi CSRF.

Richieste che modificano dati

Per operazioni come creazione, aggiornamento o cancellazione, è buona pratica usare una protezione CSRF. Un approccio comune consiste nel ricevere dal server un token CSRF e inviarlo in un header personalizzato.

async function deleteAccount(csrfToken) {
  const response = await fetch("/api/account", {
    method: "DELETE",
    credentials: "same-origin",
    headers: {
      Accept: "application/json",
      "X-CSRF-Token": csrfToken
    }
  });

  if (!response.ok) {
    throw new Error("Cancellazione non riuscita");
  }

  return response.json();
}

Il token CSRF non sostituisce il cookie di sessione: i due meccanismi lavorano insieme. Il cookie identifica la sessione, mentre il token dimostra che la richiesta proviene da una pagina autorizzata.

Best practice

  • Usa sempre HTTPS in produzione, soprattutto quando vengono trasmesse credenziali di sessione.
  • Preferisci cookie HttpOnly per i dati di sessione, così non sono direttamente leggibili dagli script.
  • Non usare Access-Control-Allow-Origin: * insieme a credentials: "include".
  • Configura CORS soltanto per le origini realmente necessarie.
  • Controlla sempre response.ok: una risposta HTTP 401 o 500 non genera automaticamente un errore JavaScript.
  • Non inserire informazioni sensibili nei messaggi mostrati all’utente o nei log del browser.
  • Per richieste che modificano dati, adotta una protezione CSRF e verifica il token sul server.
  • Centralizza la configurazione in una funzione wrapper, evitando di duplicare credentials e gli header in ogni chiamata.

Riepilogo

La Fetch API può lavorare con cookie di sessione grazie all’opzione credentials. Con richieste sulla stessa origine, same-origin è spesso sufficiente; per comunicazioni cross-origin è necessario include, oltre a una configurazione CORS corretta sul server.

La sicurezza richiede una collaborazione tra frontend e backend: cookie con attributi appropriati, HTTPS, regole CORS restrittive e protezione CSRF per le operazioni che modificano dati. Infine, ricordati che Fetch risolve la Promise anche per gli errori HTTP: il controllo di response.ok rimane indispensabile.

Approfondisci con risorse ufficiali

SHARE