Linux Day Napoli 2017 - JSON Web Tokens - RFC 7519 - Programma il Futuro

Slide del talk di Mario Rossano CTO/CEO Netlogica durante il Linux Day presso l'Aula Magna "Leopoldo Massimilla" di Ingegneria - Univ. Federico II.
JSON Web Tokens (JWT) è una tecnologia utilizzata per l'autenticazione utente che non necessita di sessioni - e quindi di frequenti query al database - con conseguente incremento della scalabilità e riduzione della complessità a vantaggio anche della cybersecurity del sistema.
Un dato da sottolineare è che JWT è uno standard (https://tools.ietf.org/html/rfc7519), con un flusso ben preciso, non occorrerà quindi "reinventare" la ruota per l'implementazione essendovi peraltro classi nei più diffusi linguaggi di programmazione.
In JWT le informazioni sono impacchettate in formato JSON, inoltre implementa diversi algoritmi crittografici per la verifica delle informazioni che trasporta - dispone di un MAC (Message Authentication Code) integrato ed è self-contained trasportando con sé tutti i dati necessari al suo utilizzo.
Un JSON Web Token si presenta come una stringa data dalla concatenazione di tre stringhe codificate in base64: header, payload e signature.
l'header dichiara il tipo (JWT) e l'algoritmo crittografico utilizzato.
{ "typ": "JWT", "alg": “HS256” }
Payload contiene le informazioni da trasmettere.
{ "id": "1234567", "name": “John Doe”, "role": “admin” }
Signature è la firma per la verifica dei dati, costruita mediante hash della concatenazione di header e payload utilizzando un sale crittografico - una chiave segreta.
var signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), 'secret');
Di seguito un esempio in Perl per l'implementazione di JWT a chiave simmetrica ed asimmetrica.
jwtSender.pl stampa a video un form in cui l'utente inserisce i propri dati personali e seleziona il tipo di algoritmo crittografico da utilizzare per la firma.
jwtReceiver.pl riceve il token da jwtSender.pl, verifica le informazioni e stampa a video i dati ricevuti se la firma è verificata.
jwtSender.pl #!/usr/bin/perl -w use strict; use warnings; use feature 'say', 'switch'; use CGI ':standard'; use CGI::Carp 'fatalsToBrowser'; use LWP::UserAgent; use Data::Dumper; use Crypt::JWT qw(encode_jwt); # inizializza output print "Content-Type: text/html\n\n"; # chiave my $privateKey; my %key; # lettura dati da inviare nel formato JWT my $queryString = CGI->new(); my $cryptoAlgorithm = $queryString->param('algoritmo'); my $nome = $queryString->param('nome'); my $cognome = $queryString->param('cognome'); my $anni = $queryString->param('anni'); # costruzione JWT: valorizzare hash payload, key, alg if ($cryptoAlgorithm eq 'HS256') { $privateKey = 'YLugdUWAY9'; %key = ( key => $privateKey ) } else { #RS256 $privateKey = " -----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWN.... -----END RSA PRIVATE KEY----- "; %key = ( key => \$privateKey ); } my %payload = ( payload => { nome => $nome, cognome => $cognome, anni => $anni } ); my %alg = ( alg => $cryptoAlgorithm ); my $token = encode_jwt( %payload, %key, %alg ); # output say 'Signing -> send JWT to server
'; say $token; my @jwtData = split(/\./,$token); say '
Payload '.$jwtData[0]; say '
Key '.$jwtData[1]; say '
Alg '.$jwtData[2]; say '
Signed with key '. $privateKey; exit;
jwtReceiver.pl #!/usr/bin/perl -w use strict; use warnings; use feature 'say'; use CGI ':standard'; use CGI::Carp 'fatalsToBrowser'; use Data::Dumper; use Crypt::JWT qw(decode_jwt); my $receivedData; # inizializza output print "Content-Type: text/html\n\n"; my $output; my $queryString = CGI->new(); my $token = $queryString->param('token'); my $alg = $queryString->param('alg'); my $key; if ($alg eq 'HS256') { $key = 'YLugdUWAY9' } else { #RS256 $key = " -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBi... -----END PUBLIC KEY----- " } if ($token ne '' && $alg eq 'RS256') { $receivedData = decode_jwt( token => $token, key => \$key, verify_exp => 0 ); } else { $receivedData = decode_jwt( token => $token, key => $key, verify_exp => 0 ); } $output .= '
Nome: '.$receivedData->{nome}; $output .= '
Cognome: '.$receivedData->{cognome}; $output .= '
Età: '.$receivedData->{anni}; say $output; say '
Verified with key '. $key; exit;
L'importanza di JWT in un'ottica di scalabilità
Occorre innanzitutto ricordare che HTTP (come HTTPS) è un protocollo stateless che non si occupa quindi di "ricordare" e gestire in alcun modo lo stato dell'utente. Per ovviare si utilizzano le "sessioni", costituite da un oggetto server side (contenente ad esempio l'ID utente salvato in un database) e da un cookie che è un oggetto client side (contenente ad esempio l'id dell'oggetto server side).
In un'architettura minima client-server lo schema di comunicazione è il seguente:
Se però l'architettura è solo di poco più complessa (e solitamente lo è) anche in una semplice configurazione con due - o più - server per un bilanciamento del carico oppure in un'architettura a microservices, lo schema precedente non funziona più. L'utente loggato sul server A viene redirezionato sul server B che però ignora il suo status e nel migliore dei casi richiederà un nuovo login all'utente.
Al problema si è ovviato interponendo un proxy che gestisce le transazioni e provvede ad uniformare le sessioni. Questo tipo di soluzione espone però un punto debole: la complessità aumenta a discapito della scalabilità.
JWT permette invece di gestire le transazioni client-server in modo più efficiente e sicuro a vantaggio della scalabilità del sistema. Il server verifica le credenziali al login dell'utente e rilascia un token (non più un id di sessione salvato nel db dove dovrebbe poi essere ripreso alla successiva richiesta dell'utente unitamente ad un cookie che dovrà salvare l'utente). L'utente salva il token su localstorage e per tutte le successive richieste invierà il token che sarà verificato on the fly server side e senza alcuna chiamata al database server.
La verifica del token è quella classica di un MAC e sarà gestita dall'apposito metodo della classe JWT.
Vantaggi
- Stateless: non occorre gestire sessioni e tutto quanto ne consegue, come le frequenti query al database server;
- Portabile: un solo token può essere riutilizzato su differenti backend, domini, applicazioni;
- Cookieless: sul client il token può essere salvato dove si vuole: localStorage, indexDB, ecc.;
- Mobile friendly: implementabile ovunque, su web, su app native (per cui la gestione cookie avrebbe richiesto uno sforzo aggiuntivo);
- Built-in Expiration: dichiarando il claim exp nell’header;
- CORS friendly: Cross-Origin Resource Sharing;
- Decentralizzato: il token può essere generato ovunque;
- Performance: cercare sessioni nel db ed estrarre le informazioni è dispendioso rispetto al calcolo di un HMACSHA256;
- Standard: le specifiche RFC7519 sono state implementate in numerosi ambienti.
Desidero segnalare che la signature può essere effettuata anche con un algoritmo di crittografia a chiave pubblica, ad esempio RSA, separando quindi le chiavi pubblica e privata.
Classe JWT
Le classi per l'implementazione di JSON Web Token sono disponibili nei principali linguaggi, ad esempio, C, C++, Swift, Java, Javascript, Node.js, Perl, Ruby, .NET, PHP, ecc. Una lista aggiornata è disponibile su https://jwt.io/
Programma il Futuro
JWT ha il suo campo di applicazione, come si è visto, nell'autenticazione utente in architetture complesse ma non è il solo ambito. Avendo un MAC integrato è possibile utilizzarlo anche per verificare l'autenticità delle informazioni che si ricevono per qualsiasi finalità. Perché preferirlo ad altre implementazioni MAC? Perchè è uno standard!
Netlogica ha implementato JWT su Programma il Futuro (progetto congiunto CINI - MIUR - Code.org per la diffusione delle basi dell'informatica nella scuola italiana che nell'a.s. 2016/17 ha coinvolto oltre 1.600.000 studenti) al fine di verificare la veridicità dei dati dei donatori comunicati dalla piattaforma di Crowdfunding della TIM (https://sostieni.programmailfuturo.it/) per effettuare poi una serie di operazioni a valle.
JWT ci permette quindi di dire "bye bye" alle session, fornendo una tecnologia più sicura, scalabile e standard. Inoltre è naturalmente "mobile-friendly" potendo essere facilmente implementato nelle app (native e non). Non resta quindi che utilizzarlo da subito nei nostri software :)
Video del Talk al Linux Day Napoli 2017