Uno degli aspetti in cui ASP.NET Core ha ricevuto un'importante rivisitazione è quello relativo alla sicurezza e al flusso di autenticazione. Esso è basato su una collezione di AuthenticationHandler, ossia di classi che, a turno, ispezionano la richiesta per verificare se il suo contenuto consenta di autenticare l'utente. Nel caso questa operazione abbia successo, il risultato è la creazione di un ClaimsPrincipal nel contesto della richiesta, che può essere poi utilizzato per la successiva autorizzazione.
ASP.NET Core fornisce una serie di AuthenticationHandler che permettono di integrare facilmente diverse logiche e provider: per esempio, la classe CookieAuthenticationHandler crea un ClaimsPrincipal se è presente un determinato security cookie, mentre JwtBearerHandler ispeziona gli header alla ricerca di un bearer token valido.
Ovviamente si tratta di un sistema perfettamente estendibile, tant'è che con poco sforzo possiamo creare un nostro handler personalizzato.
Immaginiamo per esempio di voler autenticare l'accesso alla nostra API tramite una specifica chiave passata in querystring. Come prima cosa, dobbiamo definire una classe ApiKeyOptions che conterrà tutte le informazioni necessarie per configurare il nostro sistema di security con il nome della chiave e il valore atteso.
public class ApiKeyOptions : AuthenticationSchemeOptions { public string KeyName { get; set; } public string KeyValue { get; set; } }
Questa classe deve ereditare da AuthenticationSchemeOptions, che espone delle ulteriori proprietà il cui utilizzo, però, va al di là di quanto tratteremo in questo script.
A questo punto, possiamo dedicarci all'handler vero e proprio, che dovrà ereditare da AuthenticationHandler
internal class ApiKeyHandler : AuthenticationHandler<ApiKeyOptions> { public ApiKeyHandler(IOptionsMonitor<ApiKeyOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { } }
Questa classe base permette di personalizzare diversi aspetti del processo di autenticazione, come ad esempio decidere cosa fare nel caso in cui l'autenticazione fallisca. Per il nostro esempio, tuttavia, è sufficiente implementare il metodo HandleAuthenticateAsync come nello snippet in basso:
protected async override Task<AuthenticateResult> HandleAuthenticateAsync() { if (!this.Context.Request.Query.ContainsKey(this.Options.KeyName)) { // nessuna key, quindi non possiamo autenticare l'utente return AuthenticateResult.NoResult(); } if (this.Context.Request.Query[this.Options.KeyName] == this.Options.KeyValue) { var claims = new List<Claim>() { new Claim(ClaimTypes.Name, "ApiUser") }; var scheme = this.Scheme.Name; var identity = new ClaimsIdentity(claims, scheme); var principal = new ClaimsPrincipal(identity); var ticket = new AuthenticationTicket(principal, this.Scheme.Name); return AuthenticateResult.Success(ticket); } return AuthenticateResult.Fail("Invalid key"); }
La logica è piuttosto semplice, ed è sostanzialmente volta a ritornare l'AuthenticateResult più appropriato. Nel nostro caso:
- Se la chiave non è presente nella querystring, restituiamo un NoResult. Questo perchè il nostro handler non è quello più idoneo per processare l'autenticazione. Se nessuno degli handler presente nella pipeline è in grado di autenticare l'utente, il contesto sarà quello di un utente anonimo.
- Se la chiave è presente e valida, costruiamo un ClaimsPrincipal con un nome fittizio e lo utilizziamo per ritornare un AuthenticateResult che indichi il successo dell'operazione. In un caso reale, magari, potremmo in realtà effettuare una ricerca su un database e associare una chiave a uno specifico utente.
- Se la chiave è presente, ma invalida, restituiremo invece un messaggio di failure per "Invalid Key".
Per rendere semplice l'utilizzo del nostro ApiKeyHandler, conviene anche costruire un opportuno extension method che ne agevoli la configurazione:
public static class ApiKeyExtensions { public const string ApiKeyScheme = "ApiKey"; public static AuthenticationBuilder AddApiKey(this AuthenticationBuilder builder, Action<ApiKeyOptions> configureOptions) { return builder.AddScheme<ApiKeyOptions, ApiKeyHandler>(ApiKeyScheme, displayName:null, configureOptions); } }
Per utilizzarlo, non dobbiamo far altro che sfruttare questo metodo nella classe Startup, come nel codice in basso
public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(ApiKeyExtensions.ApiKeyScheme) .AddApiKey(options => { options.KeyName = "secretKey"; options.KeyValue = "secretValue"; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
e successivamente richiedere un utente autenticato tramite l'attributo Authorize:
[Authorize] [Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { }
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Usare i servizi di Azure OpenAI e ChatGPT in ASP.NET Core con Semantic Kernel
Generare HTML a runtime a partire da un componente Razor in ASP.NET Core
Rinnovare il token di una GitHub App durante l'esecuzione di un workflow
Eseguire operazioni sui blob con Azure Storage Actions
Triggerare una pipeline su un altro repository di Azure DevOps
Sfruttare MQTT in cloud e in edge con Azure Event Grid
Potenziare Azure AI Search con la ricerca vettoriale
Gestione degli stili CSS con le regole @layer
Sfruttare al massimo i topic space di Event Grid MQTT
Supportare lo HierarchyID di Sql Server in Entity Framework 8
Utilizzare gRPC su App Service di Azure
Eseguire i worklow di GitHub su runner potenziati
I più letti di oggi
- Effettuare il log delle chiamate a function di GPT in ASP.NET Web API
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!
- Utilizzare il metodo CountBy di LINQ per semplificare raggruppamenti e i conteggi
- Creare una libreria CSS universale: Cards
- Eseguire script pre e post esecuzione di un workflow di GitHub