Utilizzare JSON Web Token per gestire le richieste fra due parti.

 

01000011 01001001 01000001 01001111 Developer!
Lo so, oggi sono un pò criptico, e proprio parlando di crittografia, vorrei scrivere e, in maniera molto semplice, implementare, un sistema di verifica degli accessi non autorizzati basato su JWT.

JWT, acronimo di JSON Web Token, è, tecnicamente un oggetto JSON definito nello standard RFC7519, che rappresenta un modo compatto e URL-safe per gestire il trasferimento di richieste fra du parti.
Con un esempio molto banale, rappresenta quello che per facebook è un access token quando si cerca di accedere alla graph API.
Data la compattezza dell'oggetto e la possibilità di essere facilmente verificato, mediante una chiave segreta di crittazione, il JWT risulta essere molto utile per gestire le richieste ad un servizio web o ad un server in generale, garantendo l'autenticità dell'utente che effettua la richiesta.

La struttura del JWT è molto molto semplice, ed è la seguente:

header.payload.signature

Solitamente, la procedura prevede che l'utente si autentichi su un server di autenticazione, il quale crea il JWT che viene "consegnato" all'utente, e che sarà il "lasciapassare" per effettuare tutte le richieste al server dell'applicazione. Dal momento che il server di autenticazione e quello di applicazione, conoscono la chiave segreta, la verifica del JWT sarà banalmente effettuata. https://medium.com/vandium-software/5-easy-steps-to-understanding-json-web-tokens-jwt-1164c0adfcec

La procedura è sintetizzata dalla seguente immagine:
Procedura di creazione e verifica jwt

Il JWT è composto, come detto da 3 parti, cioè header, payload e signature.
In particolare, l'header è così strutturato:


{
	"alg":"HS256",
	"typ":"JWT"
}

dove alg indica l'algoritmo che si utilizzerà per l'hashing della signature, nel nostro caso HMAC-SHA256, che utilizza una chiave segreta, e typ indica che l'oggetto è un JWT

Si passa poi a formare il payload, che sarebbe, in breve, una serie di informazioni utili che si vogliono inserire nella creazione del token, rappresentate nel formato JSON. C'è da dire che anche se non cè un limite alla quantità di dati che si possono inserire nel payload, più dati esso conterrà, più il JWT sarà lungo (e quindi maggiore il carico computazionale per il suo calcolo)

Infine, si calcola la signature, cioè la "firma" vera e propria del jwt, nel seguente modo:


	signature = SHA256(header + "." + payload, chiave_segreta)

Perchè sia possibile avere questa verifica, è necessario implementare una piccola classe che esponga 2 metodi, ovvero

  • Creare il JWT, a partire dai dati da inserire nel payload
  • Verificare che un token JWT fornito sia valido

 

Perchè tutto funzioni correttamente sia l'header sia il payload devono essere codificati in base64, e resi url-safe sostituendo alcuni caratteri speciali. Andiamo quindi a creare le funzioni (questa volta ho scelto il c#, tanto per cambiare e dare un pò di "lustro" a zio Microsoft...


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.IO;
using System.Net;

private static string base64URLencode(string s)
{
byte[] b = Encoding.UTF8.GetBytes(s);
s = Convert.ToBase64String(b);
s = s.Replace("+", "-").Replace("/", "_").Replace("=", "");
return s;
}
private static string base64URLencode(byte[] b)
{
string s = Convert.ToBase64String(b);
s = s.Replace("+", "-").Replace("/", "_").Replace("=","");
return s;
}
private static string createJWTToken(string username)
{
//Inizializzo l'encrypter con l'algoritmo HMAC-SHA256
HMACSHA256 encrypter = new HMACSHA256(Encoding.UTF8.GetBytes("MYBEAUTIFULSECRETKEY"));
//Calcolo l'header e il payload in base64
string b64Header = base64URLencode("{\"alg\":\"HS256\",\"typ\":\"JWT\"}");
string b64payload = base64URLencode("{\"name\":\"" + username + "\"}");
//li fondo in un'unica stringa string result = b64Header + "." + b64payload;
//Creo un hashing della stringa risultante, e lo converto in base64
string finalHMACS = base64URLencode(encrypter.ComputeHash(Encoding.UTF8.GetBytes(result)));
//giustappongo il valore della signature all'header e payload e ritorno il risultato.
return result + "." + finalHMACS;
}
private static bool JWTVerifyToken(string jwttoken, string username)
{
//Ti spiezzo in due... no, in tre ! :)
string[] pieces = jwttoken.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries);
//inizializzo l'encrypter HMACSHA256 encrypter = new HMACSHA256(Encoding.UTF8.GetBytes("MYBEAUTIFULSECRETKEY"));
//Ricavo il payload dalla stringa in base 64
byte[] data = Convert.FromBase64String(pieces[1]);
string payload = Encoding.UTF8.GetString(data);
Dictionary<string, object>
userdata = ConvertJSON2Dictionary(payload);
//Calcolo l'hashing in base ai dati passati nel token, e allo username (in questo caso, in quanto fa parte del payload, ma potrebbero essere benissimo più dati)
string finalHMACS = base64URLencode(encrypter.ComputeHash(Encoding.UTF8.GetBytes(base64URLencode(pieces[0]) + "." + base64URLencode(pieces[1]))));
//Conditio sine qua non: l'hash appena calcolato DEVE essere uguale alla signature passata come parametro a questa funzione
return (userdata["name"].ToString().Trim() == username.Trim() && pieces[2] == finalHMACS);
}
private static Dictionary<string, object> ConvertJSON2Dictionary(string json)
{
try
{
return JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
}
catch (Exception ex)
{
Dictionary<string, object> p = new Dictionary<string, object>();
p.Add("error", ex.Source + "\r\n" + ex.Message); return p;
}
}

Stando a ciò che abbiamo scritto sopra, ho implementato 3 funzioni di supporto, 2 per convertire in base64 sia una stringa, sia un vettore di bytes, e una funzione che converte una stringa JSON in un Dizionario chiave=>valore (che è il modo migliore di rappresentare JSon con .NET). Si tenga presente che è stata utilizzata la libreria di supporto Newtonsoft.Json.dll (che potete trovare in allegato insieme all'implementazione).

Per quanto riguarda l'implementazione fornita, ho realizzato una classe jwt Apposita, che si occupa di creare un token jwt, o verificare la firma di un token fornito in input. 

Per le funzioni invece principali, notiamo che una crea un token JWT mediante la teoria espressa precedentemente, mentre l'altra esegue una verifica utilizzando la creazione di un token JWT e confrontandolo con quello in input (la chiave segreta, conosciuta solo dal server dell'applicazione e dal server di autenticazione, garantisce l'assenza di richieste non autenticate).

... Ed eccoci arrivati anche stavolta alla fine dell'articolo. Come di consueto, metto a disposizione queste risorse in maniera gratuita, e sarei davvero contento se tu mi aiutassi a portare avanti la scrittura di nuovi articoli con contenuti di qualità! Qui sotto, se vuoi, puoi offrirmi un caffè!

 

Donazione per 7software.it | 1 EUR

 

Ciao Developer!
P.S. 01000011 01001001 01000001 01001111 = CIAO, IN ASCII con sistema binario :)