Il est possible de sécuriser vos WebAPi de façon efficace avec une clé d’APi d’authentification. En quelques étapes cela peut être réglé.
Dans ce tutoriel, je vais vous expliquer comment sécuriser vos WebAPI ASP.NET Core avec deux solutions différentes : utilisation d’un middleware et utilisation d »un Custom Attribute.
A noter que ce n’est pas une solution « solide » que je vous propose. Si vous souhaitez par contre utiliser une authentification et donc par extension des token,vous devriez donc utiliser des mécanismes tels que 0Auth 2.0.
Commençons donc ce tutoriel et ouvrez Visual Studio 2019 et créez une application ASP.NET Core Web en vous assurant que vous utilisez la dernière version de Visual Studio 2019.
et nommez le avec quelque chose de parlant comme ‘SecuringWebApiUsingApiKey’.
Sur l’écran suivant, choisissez le template d’API comme suit en prenant soin de choisir ASP .NET Core 5.0 :
Laissez tout tel quel, nous allons réutiliser le template par défaut avec le WeatherForecastController.
Passons maintenant à la première façon de sécuriser son API.
Utiliser un Custom Attributes
Créez une nouvelle classe nommée ApiKeyAttribute comme suit :
Avec le code suivant :
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;
namespace SecuringWebApiUsingApiKey.Attributes
{
[AttributeUsage(validOn: AttributeTargets.Class)]
public class ApiKeyAttribute : Attribute, IAsyncActionFilter
{
private const string APIKEYNAME = "ApiKey";
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.HttpContext.Request.Headers.TryGetValue(APIKEYNAME, out var extractedApiKey))
{
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Api Key was not provided"
};
return;
}
var appSettings = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
var apiKey = appSettings.GetValue<string>(APIKEYNAME);
if (!apiKey.Equals(extractedApiKey))
{
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Api Key is not valid"
};
return;
}
await next();
}
}
}
Expliquons maintenant le code :
Ceci est le décorateur qui sera utilisé dans notre cas dans le controleur.
[AttributeUsage(validOn: AttributeTargets.Class)]
AttributeTargets est une enum qui applique des flags pour filtrer et dans notre cas, ce sera dans les classes ou les méthodes car nous utilisons les pipes( | ) pour enchainer les filtres.
[AttributeUsage(validOn: AttributeTargets.Class | AttributeTargets.Method)]
Passons à la suite du code :
if (!context.HttpContext.Request.Headers.TryGetValue(APIKEYNAME, out var extractedApiKey))
{
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Api Key was not provided"
};
return;
}
Dans le code ci-dessus, nous allons extraire la valeur de la clé contenue dans le header pour voir si elle existe au minimum.
Si ce n’est pas le cas nous renverrons un code statut 401 avec un message.
Si la clé existe, nous passons à l’étape suivante qui est la validation de la clé et de sa valeur (valeur que nous avons stocké dans notre fichier de configuration).
Il existe plusieurs moyens de lire un fichier de configuration (StartUp, injection de dépendances) mais nous sommes allés au plus simple :
var appSettings = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
var apiKey = appSettings.GetValue<string>(APIKEYNAME);
Nous avons donc la valeur de notre clé en variable et allons pouvoir la comparer avec ce que nous avons reçu :
if (!apiKey.Equals(extractedApiKey))
{
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Unauthorized client"
};
return;
}
Et si la clé ne correspond pas, renvoyer un code 401 également avec un message différent.
Pour utiliser cet attribut, il suffit de « décorer » votre controleur avec l »attibut comme suit pour le sécuriser :
[ApiController]
[ApiKey]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
Utiliser un custom middleware
Le middleware se chargera d’intercepter toutes les requêtes qui passent et effectuer les actions qu’on lui aura défini.
Commençons par créer la classe et le fichier de classe comme suit :
Et le code suivant :
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
namespace SecuringWebApiUsingApiKey.Middleware
{
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
private const string APIKEYNAME = "ApiKey";
public ApiKeyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
if (!context.Request.Headers.TryGetValue(APIKEYNAME, out var extractedApiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Api Key was not provided. (Using ApiKeyMiddleware) ");
return;
}
var appSettings = context.RequestServices.GetRequiredService<IConfiguration>();
var apiKey = appSettings.GetValue<string>(APIKEYNAME);
if (!apiKey.Equals(extractedApiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Unauthorized client. (Using ApiKeyMiddleware)");
return;
}
await _next(context);
}
}
}
La logique est la même que pour l’attribut donc je ne vais pas la détailler 🙂
Pour pouvoir l’utiliser, il vous faudra l’enregistrer dans la classe de startup.cs comme défini ci-dessous :
Conclusion
Nous avons donc vu 2 façons de sécuriser une API de manière « basique » mais celà ouvre des possibilités sur l’utilisation des Attributes et des custom middlewares pour effectuer des actions autres que la sécurisation comme les logs des évènements, la réécriture des messages d’erreur pour un front etc
Pour les sources de la solution, ça se passe ici.
Bon code !
Articles similaires