31-3 Authorization et protection de l'API
Bien qu'il est maintenant possible de retourner un jeton pour un utilisateur, aucun point de terminaison de l'API n'est sécurisé.
Autrement dit, il est possible de s'authentifier, mais l'API n'en tient pas compte lors des requêtes.
Utiliser l'attribut [Authorize]
Tout comme dans MVC
, l'attribut [Authorize]
peut être utilisé sur une action d'API pour en restreindre l'accès.
Par exemple, pour la méthode Create
d'un événement.
[HttpPost]
[Authorize]
public async Task<IActionResult> Create(EvenementDto evenementDto)
{
Evenement evenement = _mapper.Map<Evenement>(evenementDto);
evenement = await _evenementService.Create(evenement);
return Ok(_mapper.Map<EvenementDto>(evenement));
}
Configuration Authentification et Autorisation
Pour que l'API puisse utiliser correctement l'attribut [Authorize]
en plus de récupérer le jeton pour le valider, quelques configurations sont nécessaires.
Ajouter AddAuthentication
à Program.cs
La première configuration, est d'ajouter à la liste de services l'authentification (AddAuthentication
) tout en précisant que l'authentification doit utiliser le principe de Bearer JWT
.
//...
// Identity
builder.Services.AddScoped<IRoleStore<ApplicationRole>, RoleRepository>();
builder.Services.AddScoped<IUserStore<ApplicationUser>, UserRepository>();
builder.Services
.AddIdentity<ApplicationUser, ApplicationRole>();
builder.Services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
});
//...
Ajouter AddJwtBearer
à Program.cs
On peut ensuite enchainer à AddAuthentication
les paramètres de validation du jeton, soit AddJwtBearer
.
//...
builder.Services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["JwtIssuer"],
ValidAudience = builder.Configuration["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSecurityKey"])),
};
});
//...
Cela fera en sorte qu'à chaque requête reçue, l'en-tête HTTP
Authorization
sera inspecté pour voir si elle contient un jeton JWT.
Si tel est le cas, le jeton sera décodé et validé.
ValidateIssuer
vérifie si leissuer
du jeton est valideValidateAudience
vérifie si la requête vient d'uneaudience
valideValidateLifetime
vérifie si le jeton est encore valide (temps)ValidateIssuerSigningKey
vérifie si le jeton est valide (signature)
Les autres paramètres sont les configurations utilisées pour la validation des différents aspects mentionnés.
Ajouter UseAuthentication
et UseAuthorization
à Program.cs
Finalement, on peut dire à l'application de faire usage de l'authentification et de l'autorisation précédemment configurée.
//...
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapFallbackToFile("index.html");
//...
Il est possible que vous ayez déjà une ligne UseAuthorization()
dans votre fichier Program.cs
. Assurez-vous de l'enlever ou de la déplacer à l'endroit proposé.
Test de l'autorisation
401 - Unauthorized (sans jeton valide)
En assumant que vous ayez protégé l'action Create
des événements, vous pouvez réessayer dans Postman
de créer un événement.
Vous devriez recevoir une erreur 401 (unauthorized)
200 - OK (avec jeton valide)
Assurez-vous d'avoir récupéré un jeton valide dans un premier temps à partir du point de terminaison de connexion.
Maintenant, si vous répétez l'opération, mais en précisant un jeton valide dans la portion Authorization
de Postman
:
Vous devriez avoir une réponse de succès!
Validation des rôles
La dernière étape consiste à valider les rôles. En effet, non seulement il faut être authentifié pour créer un événement, mais il faut en plus avoir un certain rôle, soit le rôle d’administrateur.
Préciser le ou les rôles dans l'attribut [Authorize]
En précisant le ou les rôles acceptés pour une action, on peut en restreindre l'accès.
Par exemple, ici on restreint à un rôle ADMIN
.
[Authorize(Roles = "ADMIN")]
[HttpPost]
public async Task<IActionResult> Create(EvenementDto evenementDto)
{
Evenement evenement = _mapper.Map<Evenement>(evenementDto);
evenement = await _evenementService.Create(evenement);
return Ok(_mapper.Map<EvenementDto>(evenement));
}
Si vous essayez en n'ayant pas le bon rôle dans votre jeton, vous aurez une erreur 403 Forbidden
.
Vous pouvez créer un nouveau rôle dans la table application_roles
et ensuite l'associer à votre utilisateur via la table application_roles_users
.
Vous devrez récupérer un nouveau jeton via l'authentification si vous modifiez le rôle associé à votre utilisateur! En effet, les rôles sont stockés dans le jeton JWT, alors pour que votre nouveau rôle soit pris en compte, il faut obtenir un nouveau jeton.
Si vous réessayez de créer un événement, le tout devrait fonctionner maintenant que vous avez le bon rôle admin!
Je vous recommande d'ajouter à votre seed utilisateurs une entrée pour un utilisateur admin et lui attribuer le bon rôle.
Protection des autres points de terminaison
Comme la majeure partie des actions du contrôleur d'événement sont protégées, on peut assigner au niveau du contrôleur l'attribut [Authorize]
, de cette façon:
[Authorize(Roles = "ADMIN")]
[Route("api/[controller]")]
[ApiController]
public class EvenementsController : ControllerBase
Ensuite, pour les actions Index
et Show
qui ne nécessitent pas d'être authentifié, l'attribut [AllowAnnonymous]
peut être ajouté.
[AllowAnonymous]
[HttpGet]
public async Task<IActionResult> Index()
{
//...
}
//...
[AllowAnonymous]
[HttpGet("{id:int}")]
public async Task<IActionResult> Show(int id)
{
//...
}