Usare ASP.NET Identity per autenticare i client di un'applicazione ASP.NET Web API

di Moreno Gentili, in ASP.NET Web API,

Nei precedenti script abbiamo sempre esaminato ASP.NET Identity in relazione a progetti ASP.NET MVC e WebForms. Tuttavia, anche quando realizziamo un'applicazione ASP.NET Web API, abbiamo la stessa necessità di identificare i client per regolarne l'accesso alle risorse ed, eventualmente, imporre dei limiti di utilizzo.

In uno scenario in cui i client consistono di altre applicazioni server-side che accedono alla nostra Web API, non esiste alcuna interfaccia web in cui digitare username e password. Per questo motivo, lasceremo che i client si autentichino mediante Basic Authentication, un metodo che prevede l'invio delle credenziali con l'intestazione Authorization di ogni richiesta HTTPS. E' quantomai necessario procurarsi un certificato SSL affinché la comunicazione con il server avvenga in maniera sicura, dato che tali credenziali viaggiano in chiaro.

Iniziamo installando da NuGet il pacchetto di ASP.NET Identity che si occupa della persistenza degli utenti locali. Dalla console di installazione pacchetti di Visual Studio, digitiamo:

Install-Package Microsoft.AspNet.Identity.EntityFramework

Dato che in questo scenario non è previsto alcuno scambio di cookie di autenticazione, possiamo evitare di installare il pacchetto Microsoft.AspNet.Identity.OWIN, la cui specifica responsabilità è proprio quella di emettere ed elaborare i cookie.

All'arrivo di una richiesta del client, esamineremo le sue credenziali da un DelegatingHandler, come già visto in un precedente script di Marco De Sanctis:
https://www.aspitalia.com/script/1134/Proteggere-Chiave-Servizio-ASP.NET-Web-API.aspx

Questo componente si inserisce nella pipeline di ASP.NET Web API prima dell'esecuzione di un'action e ci dà l'opportunità di identificare il client prima che venga eseguita qualsiasi logica di business. Se il client non dovesse risultare autorizzato, la richiesta ad un'action protetta dall'attributo Authorize verrebbe immediatamente bloccata.


Per creare il DelegatingHandler, creiamo una classe denominata BasicAuthHandler in una cartella del nostro progetto. Ad esempio: /Handlers.

public class BasicAuthHandler : DelegatingHandler
{
  protected override async Task<HttpResponseMessage> 
    SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  {
      string username, password;

      // Se l'intestazione Authorization non è stata fornita, terminiamo subito
      AuthenticationHeaderValue auth = request.Headers.Authorization;
      if (auth == null || auth.Scheme != "Basic")
        return await base.SendAsync(request, cancellationToken);

      string credenziali;
      try
      {
        // Il valore dell'intestazione va decodificato dalla sua forma Base64
        // Per i dettagli, vedere: 
        // http://www.w3.org/Protocols/HTTP/1.0/spec.html#BasicAA
        credenziali = Encoding.UTF8
          .GetString(Convert.FromBase64String(auth.Parameter));
      }
      catch
      {
        // Probabilmente la stringa base64 non era valida
        credenziali = string.Empty;
      }
  
      // Username e password sono separati dal carattere delimitatore :
      // Terminiamo l'esecuzione se non è presente o se è in posizione non valida
      var indiceDelSeparatore = credenziali.IndexOf(":");
      if (indiceDelSeparatore < 1 || indiceDelSeparatore > credenziali.Length-2)
        return await base.SendAsync(request, cancellationToken);

      // Estraiamo finalmente le credenziali
      username = credenziali.Substring(0, indiceDelSeparatore);
      password = credenziali.Substring(indiceDelSeparatore + 1);

      // Otteniamo un riferimento dal nostro ApplicationUserManager,
      // una nostra classe derivata da UserManager<TUser>
      // Possiamo istanziarlo o recuperarlo da un service locator
      var manager = new ApplicationUserManager();

      // Verifichiamo l'esistenza dell'utente
      var utente = await manager.FindByNameAsync(username);

      // Terminiamo l'esecuzione se l'utente è inesistente o sospeso
      if (utente == null || (utente.LockoutEnabled && 
          utente.LockoutEndDateUtc > DateTime.UtcNow))
        return await base.SendAsync(request, cancellationToken);

      // Verifichiamo se anche la password è valida
      var passwordValida = await manager
        .CheckPasswordAsync(utente, password);
      if (passwordValida)
      {
        // Credenziali valide: impostiamo un'identità per l'utente
        var principal = 
          new GenericPrincipal(new GenericIdentity(username), null);
        Thread.CurrentPrincipal = principal;
        request.GetRequestContext().Principal = principal;
      }

      return await base.SendAsync(request, cancellationToken);
  }
}

Registriamo il DelegatingHandler nella pipeline, aggiungendo la seguente riga di codice nel file /App_Start/WebApiConfig.cs.

config.MessageHandlers.Add(new BasicAuthHandler());

Non resta che utilizzare l'attributo Authorize per proteggere action o interi controller. In questo esempio, l'attributo viene usato per impedire gli accessi non autorizzati all'ApiController che offre le informazioni sul meteo.

[Authorize]
public class MeteoController : ApiController
{
  //...
}

In alternativa, possiamo proteggere l'intera Web API con una sola istruzione, registrando l'attributo Authorize globalmente, nel file /App_Start/WebApiConfig.cs.

config.Filters.Add(new AuthorizeAttribute());

Ora che la nostra applicazione ASP.NET Web API è protetta, dobbiamo disporre di un'interfaccia amministrativa che ci consenta di aggiungere le credenziali abilitate all'accesso. A tal proposito, possiamo avvalerci del pacchetto Thinktecture.IdentityManager già citato in un precedente script.
https://www.aspitalia.com/script/1185/Amministrare-Utenti-Ruoli-ASP.NET-Identity.aspx

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