modulo 08 / lezione 41

Debugging di dati, backend e integrazioni

Autenticazione, sessioni, JWT e CORS

Distinguere errori di identità, permessi, cookie (SameSite, Secure, HttpOnly), token JWT (firma, scadenza, audience), CORS preflight OPTIONS.

55 min Debugging

In parole semplici

Gli errori di sicurezza si nascondono dietro sintomi identici: “non funziona”. Sapere distinguere identità (chi sei), autorizzazione (cosa puoi fare), sessione (come ti ricordo), CORS (da dove vieni) trasforma un’ora di indagine in dieci minuti di lettura mirata.

Parole nuove

  • autenticazione: chi sei (login).
  • autorizzazione: cosa puoi fare (permessi).
  • sessione: meccanismo con cui il server ti riconosce su richieste successive.
  • cookie: piccolo dato che il browser invia automaticamente.
  • JWT (JSON Web Token): token firmato che contiene informazioni utente.
  • CORS: meccanismo che limita richieste cross-origin.

Cosa impari

  • Distinguere 401 da 403 (di nuovo, perché ci si casca sempre).
  • Leggere cookie con SameSite, Secure, HttpOnly.
  • Capire payload e claim JWT.
  • Risolvere errori CORS più frequenti.

401 vs 403 (ancora)

  • 401 Unauthorized = “Non so chi sei”. Il token manca, è scaduto, è invalido.
  • 403 Forbidden = “So chi sei, ma non puoi”. Il ruolo non ha permessi.

Sbagliare l’interpretazione porta a fix sbagliati (rinnovi token quando il problema sono i permessi, o viceversa).

Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=3600

Flag:

  • HttpOnly: il cookie non è leggibile da JavaScript. Protezione da XSS. Per token di sessione, obbligatorio.
  • Secure: il cookie viaggia solo su HTTPS. Mai usare in localhost HTTP senza eccezioni.
  • SameSite: limita l’invio cross-origin.
    • Strict: cookie inviato solo da richieste same-site.
    • Lax: inviato per navigazioni top-level (cliccare un link).
    • None: inviato sempre, ma richiede Secure.

Bug tipici:

  • Login funziona, ma su pagine successive l’utente è “scollegato”: probabile SameSite=Strict su redirect da OAuth.
  • Cookie non parte da app embeddata in iframe: serve SameSite=None; Secure.
  • Cookie in dev locale non funziona: Secure su http://localhost è ignorato in alcuni browser.

JWT: cos’è e come si legge

Tre parti separate da .:

eyJhbGc...header...eyJzdWI...payload...XYZ...signature

Decoding (base64url):

const payload = JSON.parse(atob(token.split(".")[1]));
console.log(payload);
// {
//   "sub": "user-42",
//   "name": "Carlo",
//   "iat": 1735603200,
//   "exp": 1735606800,
//   "aud": "my-app",
//   "iss": "auth.example.com"
// }

Claim standard:

  • sub: subject (id utente).
  • iat: issued at (timestamp emissione).
  • exp: expiration (timestamp scadenza).
  • aud: audience (per chi è).
  • iss: issuer (chi ha emesso).

Diagnosi:

  • Token scaduto → exp < now() → 401.
  • Audience sbagliato → aud non corrisponde → 401 o 403.
  • Firma invalida → 401.

Strumento utile: jwt.io (decodifica visiva). Attenzione: incolla solo token di test, mai produzione.

Storage del token: dove?

  • HttpOnly cookie: il più sicuro (XSS non lo legge). Server-driven.
  • localStorage: vulnerabile a XSS. Sconsigliato per token sensibili.
  • memory (variabile JS): sicuro contro XSS persistente, ma si perde a refresh.

Bug: token in localStorage, attacco XSS, attaccante esfiltra. Bug grave di sicurezza, non solo funzionale.

CORS: la quasi-magia

Browser blocca richieste cross-origin per JavaScript:

  • Stesso origin = stesso schema + host + porta.
  • https://app.example.com:443 e https://api.example.com:443 = origin diversi.

Per consentire cross-origin, il server risponde con header CORS:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

Preflight OPTIONS

Per richieste “non semplici” (es. con Content-Type: application/json, header custom), il browser fa una richiesta OPTIONS prima:

OPTIONS /api/utenti HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

Il server risponde con gli header Allow-*. Solo se ok, parte la richiesta vera.

Errori frequenti:

  • OPTIONS ritorna 404 → il server non gestisce preflight.
  • Allow-Origin: * con Allow-Credentials: true → vietato dalla spec.
  • Allow-Origin non corrisponde all’origine del browser → bloccato.

Errori CORS in console

Access to fetch at 'https://api.x.com/...' from origin 'https://app.y.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

Diagnosi:

  1. È una preflight che fallisce? Network mostra OPTIONS con 4xx.
  2. È la richiesta vera? Vedi header di risposta.
  3. Manca un header? Aggiungi lato server.

Importante: CORS è enforcement del browser. Da curl la richiesta funziona, ma dal browser no. Sintomo classico.

Esercizio guidato

  1. Apri un sito che richiede login. Cattura la response di login.
  2. Vedi Set-Cookie: quali flag ci sono? Apri Application → Cookies per ispezionare.
  3. Cerca un endpoint protetto: vedi quali header parte (Cookie, Authorization).
  4. Decodifica un JWT (anche di test, mai dati reali) e identifica exp, aud, iss.
  5. Provoca un errore CORS: prova a chiamare un’API esterna da console (senza CORS abilitato).

Errori frequenti del principiante

  • Mettere il token in localStorage senza valutare XSS.
  • Allow-Origin: * in produzione.
  • Confondere autenticazione con autorizzazione. Login non = permessi.
  • Ignorare exp. Token scaduto sembra “bug random” finché non lo decodifichi.