Autenticazione ad applicazioni ASP.NET con Hawk

di Moreno Gentili, in ASP.NET MVC, ASP.NET Web API,

Tra le tante tecnologie legate alla sicurezza per il web, oggi ci occuperemo di Hawk (contrazione di Holder-Of-Key), uno schema di autenticazione HTTP relativamente recente. Si tratta di un protocollo che consente a client e server di verificare, vicendevolmente, che richieste e risposte non siano state alterate durante il loro tragitto. Diversamente da altre soluzioni, Hawk è anche in grado di mettere il server al riparo da attacchi di tipo replay, che possono rappresentare un problema per le Web API non idempotenti.

Lo scenario di utilizzo tipico di questo protocollo è quello della comunicazione machine-to-machine, come nel caso di un'applicazione web o mobile che consumi una Web API remota.

Il protocollo Hawk lavora con una chiave segreta che client e server conoscono (pre-shared key) e che viene usata per generare un codice di sicurezza (MAC) calcolato in base al contenuto della richiesta, all'URL, e alla data/ora corrente, che verrà poi inviato come parte del messaggio nell'intestazione Authorization.
Vediamo un esempio di richiesta da parte di un client:

POST /api/products HTTP/1.0
Host: www.example.com
Content-Type: application/json
Content-Length: 40
Authorization: Hawk id="myclientid", ts="1457863978", nonce="ZdfvdM", 
mac="AqOfyGYwpljWtQR2CbMhiVEHtbag+SJtE+PSxBjQFGQ=", ext=""

{"title": "My new book", "price": 23.50}

L'intestazione Authorization qui rappresentata si compone delle seguenti parti:

  • L'identificativo del client (ma non la sua chiave segreta);
  • Uno UNIX timestamp, che non deve divergere troppo dalla data/ora del server al fine di restringere il periodo di validità della richiesta;
  • Un nonce ovvero un codice casuale e monouso che il server può decidere di tenere in considerazione se vuole evitare attacchi di tipo replay;
  • Il codice di sicurezza, che il server ricalcolerà per verificarne l'esattezza;
  • Dei dati extra, opzionali, coinvolti anch'essi nel calcolo del codice di sicurezza.

Dato che la chiave segreta non viene mai scambiata tra client e server, un ipotetico man in the middle non potrebbe alterare il messaggio e al contempo ricalcolare il codice di sicurezza appropriato. Inoltre, questo accorgimento rende l'uso di HTTPS opzionale e rende Hawk adatto anche in micro progetti, in cui procurarsi un certificato SSL non rientri nel budget. Ovviamente, scambiare dati su un canale sicuro resta sempre una pratica raccomandata, sia per garantire la confidenzialità dei dati che come ulteriore livello di sicurezza a cui ogni applicazione web dovrebbe ricorrere.

Hawk non è uno standard né dispone di una propria specifica formale ma è stato descritto dal suo pragmatico e turbolento ideatore Eran Hammer-Lahav (http://hueniverse.com/), già editore capo nel working group di OAuth, con un'implementazione in javascript che è stata successivamente portata in molti altri linguaggi dagli sviluppatori della community (https://github.com/hueniverse/hawk/issues?q=is%3Aclosed+label%3Aport).

Per applicazioni .NET, disponiamo del pacchetto HakwNet, mantenuto da Pablo Cibraro. La libreria contiene il necessario per generare l'intestazione Hawk e verificarne la validità. Sono anche disponibili ulteriori pacchetti per semplificarne ulteriormente l'utilizzo con ASP.NET Web API ed OWIN.

Configurare l'applicazione server
Andiamo dunque a proteggere con Hawk un'applicazione ASP.NET Web API. Digitiamo quanto segue dalla Package Manager Console di Visual Studio:

Install-Package HawkNet.WebApi

Ora, dal file App_Start/WebApiConfig.cs aggiungiamo la seguente configurazione:

config.Filters.Add(new HawkAuthentication(
  typeof(HawkCredentialRepository),
  timeskewInSeconds: 60,
  includeServerAuthorization: true));

dove timeskewInSeconds è il numero massimo di secondi di divergenza tra l'ora fornita dal client e quella in uso dal server e includeServerAuthorization è un booleano che indica al server di emettere esso stesso un'intestazione Hawk per il client, affinché possa validare la sua risposta.

Il tipo HawkCredentialRepository va invece implementato nel nostro progetto come segue:

public class HawkCredentialRepository : IHawkCredentialRepository
{
  //il parametro "id" rappresenta l'identificativo del client
  //fornito nell'intestazione Hawk
  public async Task<HawkCredential> GetCredentialsAsync(string id)
  {
    //TODO: qui mettere un'implementazione asincrona che
  //recuperi le HawkCredential dal database, anziché da
  //un oggetto in memoria, come mostrato in questo esempio
    var credential = credentials.SingleOrDefault(c => c.Id == id);
    return credential;
  }

  //Lista di HawkCredential residente in memoria
  //a solo scopo esemplificativo
  private List<HawkCredential> credentials = new List<HawkCredential>
  {
    new HawkCredential
    {
      Id = "myclientid",
      Key = "jhsdn4jkadb7asmlanp9m",
      Algorithm = "sha256",
      User = "Alice"
    }
  };
}

La classe HawkAuthentication, qui configurata come filtro globale, si occuperà di verificare la validità dell'intestazione Hawk fornita dal client e di autorizzare la richiesta corrente. In questo modo, potremo proteggere i nostri ApiController e le nostre action da accessi anonimi usando l'attributo [Authorize] che siamo già abituati ad usare.

Se al posto di un'applicazione ASP.NET Web API volessimo proteggere una qualsiasi applicazione OWIN-compliant, andremo invece ad aggiungere il seguente pacchetto al progetto:

Install-Package HawkNet.Owin

Per poi usare il middleware dal metodo Configuration della classe Startup. La configurazione è equivalente a quella vista in precedenza.

var options = new HawkAuthenticationOptions
{
  Credentials = GetCredentialsByIdAsync,
  TimeskewInSeconds = 60,
  IncludeServerAuthorization = true,
};
app.UseHawkAuthentication(options);

Configurare l'applicazione client
Andiamo ora ad occuparci dell'applicazione client, che dovrà generare ed includere l'intestazione Authorization nelle proprie richieste. Installiamo lo stesso pacchetto usato in precedenza:

Install-Package HawkNet.WebApi

Ora forniamo un'istanza della classe HawkClientMessageHandler al costruttore di HttpClient, come segue:

//Credenziali per l'accesso
var credential = new HawkCredential
{
  Id = "myclientid",
  Key = "jhsdn4jkadb7asmlanp9m",
  Algorithm = "sha256",
  User = "Alice"
};

var messageHandler = new HawkClientMessageHandler(
  new HttpClientHandler(), credential);

var client = new HttpClient(messageHandler);
var uri = new Uri("http://www.example.com/api/products");

//L'intestazione Authorization viene automaticamente aggiunta alla richiesta
var contenuto = await client.GetStringAsync(uri);

Non sarà richiesto alcun altro intervento manuale da parte nostra. Tuttavia, se volessimo validare la risposta del server o produrre noi stessi un'intestazione Hawk in maniera on-demand, possiamo avvalerci dei metodi statici Hawk.Authenticate ed Hawk.GetAuthorizationHeader.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi