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
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Visualizzare le change sul plan di Terraform tramite le GitHub Actions
Paginare i risultati con QuickGrid in Blazor
Eseguire script pre e post esecuzione di un workflow di GitHub
Criptare la comunicazione con mTLS in Azure Container Apps
Eseguire query manipolando le liste contenute in un oggetto mappato verso una colonna JSON
Migrare una service connection a workload identity federation in Azure DevOps
Creare una libreria CSS universale - Rotazione degli elementi
Eliminare una project wiki di Azure DevOps
Creazione di plugin per Tailwind CSS: espandere le funzionalità del framework dinamicamente
Creare un'applicazione React e configurare Tailwind CSS
Filtering sulle colonne in una QuickGrid di Blazor
Utilizzare Container Queries nominali