Vai al contenuto

Autenticazione JWT in PHP. Un esempio pratico completo (parte 1)

C’è stato un tempo in cui l’unico modo per autenticarti in un’applicazione era fornire le credenziali di accesso, passate in chiaro con una richiesta di tipo POST. Il protocollo HTTP purtroppo è stateless, cioè significa che la connessione tra client e server viene chiusa ogni volta che viene terminata la richiesta. Per superare questo ostacolo, allora veniva di solito utilizzata una particolare variabile di tipo Session. Il server quindi, alla richiesta di autenticazione, – nel caso di autenticazione positiva – rispondeva con la creazione di una variabile di sessione, che manteneva il suo stato per tutta la durata della sessione di lavoro del browser.

Purtroppo, la pratica di mantenere lo stato di un utente in una variabile di sessione, porta ad evidenti svantaggi, infatti:

  • nonostante non siano in una cartella pubblica, i dati vengono archiviati in un file di testo sul server, facilmente accessibili da chiunque abbia accesso al server
  • ogni volta che si avvia una sessione o i suoi dati vengono modificati, il server deve aggiornare il file di sessione e questo comporta un rallentamento delle prestazioni del server nel caso in cui si ha a che fare con un numero considerevole di utenti (a meno che non utilizzi archivi di sessione alternativi).
  • poiché i file di sessione sono archiviati di default nel file system, è difficile disporre di un’infrastruttura distribuita o in cluster per applicazioni che richiedono l’uso di load balancers, clustered servers, etc…

Inoltre, con l’aumentare della complessità dei sistemi informatici e con l’avvento dei sistemi cloud, le Session sono state abbandonate a favore di soluzioni più sicure e performanti, tra le quali i token e nello specifico nei token JSON (JWT).

JWT

Dal 2010 esiste JSON Web Token,  uno standard open (RFC 7519) ormai consolidato, che può essere utilizzato per trasmettere dati in modo sicuro tra due endpoint. I JWT sono più comunemente usati per l’autenticazione dell’utente ma possono anche essere utilizzati per scambiare altre informazioni in modo sicuro.

Scenari tipici

  • Autorizzazione e Autenticazione: una volta che l’utente ha effettuato l’accesso, ogni richiesta successiva includerà il JWT, consentendo all’utente di accedere a percorsi, servizi e risorse consentiti con quel token. Il Single Sign On è una funzionalità che utilizza ampiamente JWT al giorno d’oggi, grazie al suo piccolo overhead e della sua capacità di essere facilmente utilizzato in diversi domini. Il processo di autenticazione segue questi passaggi: quando un utente si autentica, il back-end del sistema invia un JWT all’utente e lo invia al lato client. Questo token contiene una firma speciale che convalida il token come emesso dal sistema. Il client memorizza il token nel browser e lo invia ad ogni richiesta al server, dove il token viene utilizzato per verificare l’autenticazione dell’utente.
  • Scambio di informazioni: poiché i JWT possono essere firmati, utilizzando ad esempio una coppia di chiavi pubblica / privata, puoi essere certo che i mittenti siano chi dicono di essere. Quindi i JWT sono un buon modo per trasmettere in modo sicuro le informazioni tra le parti. Inoltre, poiché la firma viene calcolata utilizzando l’intestazione e il payload, puoi anche verificare che il contenuto non sia stato manomesso.

La struttura del JWT

Un JWT è composto da 3 stringhe separate ciascuno da un punto. Pertanto, un JWT in genere ha il seguente aspetto.

xxxxx.yyyyy.zzzzz

dove:

  • xxxxx è l’HEADER
  • yyyyy è il PAYLOAD
  • zzzzz è la SIGNATURE

L’header (o intestazione), codificata nel token in base64url, contiene metadati sul token in formato JSON. Due campi presenti nell’intestazione sono alg e typ.

  • alg, che specifica l’algoritmo utilizzato per firmare il token durante la generazione della firma (nell’esempio in basso è stato usato l’algoritmo RS256).
  • typ, che specifica il tipo di token, che è “JWT”. Nell’esempio seguente viene mostrata un’intestazione di token tipica.
{
    "alg": "RS256",
    "typ": "JWT"
}

Payload

Il payload (o carico utile) è il messaggio che si vuole passare e rappresenta qualsiasi entità in formato JSON. Di solito, un JWT utilizzato per l’autenticazione memorizza alcune informazioni cruciali sull’utente, come l’ID utente e il ruolo utente. Di solito ha questo aspetto:

{
    "id": "323",
    "name": "gianluca",
    "role": "admin"
}

Questi campi sono chiamati claims. Esistono tre tipi di claims: registratipubblici e privati.

  • registrate : si tratta di una serie di claims predefiniti e descritti dallo standard (consultabili da questo link) che non sono obbligatori ma consigliati. Alcuni di loro sono: 
    • iss: definisce l’emittente del token
    • exp: fornisce un tempo di scadenza al token. Una volta trascorso questo tempo di scadenza, il token non è più valido
    • aud: definisce il pubblico del token
    • iat: memorizza l’ora in cui è stato emesso il token
  • pubblici: possono essere definiti a piacimento. Per evitare conflitti  tuttavia dovrebbero essere scelti dal registro IANA JSON Web Token
  • privati: queste sono claims personalizzati creati per condividere le informazioni tra le parti che accettano di utilizzarle e non sono né registrati né pubblici.

Vediamo come appare un payload con alcune attestazioni standard.

{
    "id": "1234591",
    "name": "Mary Poppins",
    "role": "editor",
    "iss": "mywebsite.com",
    "exp": 3600
}

Il payload JWT può includere tutti i campi di informazioni che desideri, ma si consiglia di mantenere i nomi più ridotti possibile. Non è necessario memorizzare informazioni sensibili come le password degli utenti poiché non è crittografato. È semplicemente codificato in codifica base64url.

Signature

L’ultima parte di un token JWT è la firma, cioè un codice di autenticazione del messaggio utilizzato per verificare che il token non sia stato modificato o generato da un estraneo ad eccezione dei server autorizzato.

La firma viene generata firmando l’header e il payload combinati utilizzando un algoritmo di crittografia e una parola chiave segreta memorizzata nel server. Solo qualcuno che ha l’header e il payload del token e la chiave segreta può generare una “frima” accettata dal server. Quindi, è importante utilizzare una chiave segreta forte per crittografare i token e archiviarli in modo sicuro sul server.

Se usiamo un algoritmo simmetrico come HMAC SHA-256, il server che emette il JWT e il server che convalida il JWT dovrebbero avere accesso sicuro alla chiave segreta. Se viene utilizzato un algoritmo asimmetrico come RS256, possiamo utilizzare un sistema di chiave pubblica-privata, in cui viene utilizzata una chiave privata per firmare il token e una chiave pubblica per convalidarlo.

Come hai già intuito, la firma è la parte più importante di un token JWT. Quando il JWT emesso viene rispedito al server con ogni richiesta dal lato client, il server controlla la firma per convalidare che si tratta di un token emesso dal sistema stesso e quindi procede a servire la richiesta del client.

JWT vs Session

Nell’autenticazione basata su session, il server crea una sessione per l’utente dopo che l’utente ha effettuato l’accesso. L’id della sessione viene quindi memorizzato su un cookie nel browser dell’utente. Mentre l’utente rimane connesso, il cookie verrà inviato insieme a ogni richiesta successiva. Il server può quindi confrontare l’ID di sessione memorizzato nel cookie con le informazioni di sessione archiviate nella memoria per verificare l’identità dell’utente e inviare la risposta con lo stato corrispondente.

Nell’applicazione basata su token, il server crea JWT con una chiave segreta e invia il JWT al client. Il client memorizza il JWT (di solito nella cache o sotto forma di cookie) e include JWT nell’intestazione in ogni richiesta da inviare al server. Il server, ricevuta la richiesta, convalida quindi il JWT e invia la risposta.

La differenza rispetto all’uso delle sessioni è è che lo stato dell’utente non viene memorizzato sul server, mentre viene invece memorizzato all’interno del token sul lato client. La maggior parte delle moderne applicazioni Web utilizza JWT per l’autenticazione.

Conclusioni

Le web app che necessitano di un modulo di autenticazione, risulta conveniente, in termini di scalabilità, l’uso di token anzichè di session, poichè le sessioni sono archiviate nella memoria del server, e ciò diventa un problema quando c’è un enorme numero di utenti che utilizzano il sistema contemporaneamente. Invece, non ci sono problemi con l’uso di token poichè questi ultimi sono archiviati sul client.

Inoltre, le sessioni sono limitate all’utilizzo sul singolo dominio, poichè l’id della sessione memorizzato nel cookie funziona sul singolo dominio (o sottodomini esistenti) e vengono normalmente disabilitati dal browser per gli altri domini (cookie di terze parti). Un problema classico riguarda, ad esempio, quando un’applicazione utilizza diverse API di diversi server su domini diversi. Invece, il token JWT viene incorporato nell’intestazione della richiesta di volta in volta, bypassando la limitazione dei cookies. Il server che crea il JWT e il server che lo convalida non deve essere per forza lo stesso. Un server può emettere JWT ed eseguire attività di autenticazione, mentre l’altro server che implementa la logica dell’applicazione può convalidare il JWT indipendentemente dal primo server.

Uno svantaggio di JWT è che la dimensione di JWT è maggiore rispetto all’ID di sessione memorizzato nel cookie perché JWT contiene più informazioni sull’utente. È necessario quindi cercare di passare solo le informazioni necessarie in JWT mentre le informazioni sensibili dovrebbero essere omesse per prevenire attacchi alla sicurezza XSS.

Nel prossimo articolo verrà spiegato come utilizzare JWT in una semplice applicazione di autenticazione implementata con PHP/MYSQL.