Aller au contenu principal

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.

Snowfall.Web.Api/Controllers/EvenementsController.cs
[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.

Snowfall.Web.Api/Program.cs
//...

// 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.

Snowfall.Web.Api/Program.cs
//...

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"])),
};
});
//...
info

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 le issuer du jeton est valide
  • ValidateAudience vérifie si la requête vient d'une audience valide
  • ValidateLifetime 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.

Snowfall.Web.Api/Program.cs
//...

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapFallbackToFile("index.html");

//...
attention

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)

Imgur

200 - OK (avec jeton valide)

info

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:

Imgur

Vous devriez avoir une réponse de succès!

Imgur

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.

Snowfall.Web.Api/Controllers/EvenementsController.cs
[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.

Imgur

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.

Imgur

Imgur

attention

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!

info

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)
{
//...
}