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
Utilizzare politiche di resiliency con Azure Container App
Eseguire le GitHub Actions offline
Popolare una classe a partire dal testo, con Semantic Kernel e ASP.NET Core Web API
Referenziare un @layer più alto in CSS
Routing statico e PreRendering in una Blazor Web App
Sfruttare i KeyedService in un'applicazione Blazor in .NET 8
Supporto ai tipi DateOnly e TimeOnly in Entity Framework Core
Ottimizzare la latenza in Blazor 8 tramite InteractiveAuto render mode
Eseguire i worklow di GitHub su runner potenziati
Disabilitare automaticamente un workflow di GitHub
Autenticarsi in modo sicuro su Azure tramite GitHub Actions
Effettuare il log delle chiamate a function di GPT in ASP.NET Web API