autorizzazione autenticazione

Registrazione e RBAC in .net API c#

Nell’articolo precedente ci siamo occupati di aggiungere JWT per l’autorizzazione alle risorse API.

In questo articolo vedremo come aggiungere la gestione degli utenti e proteggere gli ending point delle api con autenticazione, autorizzazione e ruoli.

Per semplificare definiamo i seguenti ruoli:

  • Admin
  • Manger
  • User

La parte di creazione del model, del repository e degli useCases segue quanto visto per l’api Customer. Le parti che sono modificate sono l’entità e il model utente, la parte di autorizzazione (generazione del token) e l’aggiunta di un attributo per filtrare gli accessi. Andiamo in ordine.

Definiamo prima i ruoli tramite un enum:

namespace Internal.Domain.Enums
{
    public enum UserRole
    {
        Admin,
        User,
        Manager
    }
}

Poi modifichiamo la classe User e UserModel.

using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson;

namespace Internal.Domain.Entities
{
    public class User
    {
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string Id { get; set; } = ObjectId.GenerateNewId().ToString();
        public required string Username { get; set; }
        public required string Email { get; set; }
        public required string PasswordHash { get; set; }
        public List<string> Roles { get; set; } = new List<string>();
    }
}

A questo punto nella registrazione dell’utente aggiungiamo un ruolo di default.

 public async Task<AuthTokenDto> RegisterAsync(RegisterUserDto registerUserDto)
 {
     if (await _userRepository.ExistsByEmailAsync(registerUserDto.Email))
     {
         throw new ArgumentException("Email is already in use.");
     }

     var newUser = new User
     {
         Username = registerUserDto.Username,
         Email = registerUserDto.Email,
         PasswordHash = _passwordHasher.HashPassword(null, registerUserDto.Password),
         Roles = new List<string> { UserRole.User.ToString() } // Default role
     };

     await _userRepository.AddAsync(newUser);

     // Automatically authenticate the user after registration
     var token = GenerateJwtToken(newUser);
     return new AuthTokenDto { AccessToken = token };
 }

Ora dobbiamo modificare la generazione del token per gestire i ruoli.

 private string GenerateJwtToken(User user)
 {
     var claims = new List<Claim>
     {
         new Claim(ClaimTypes.Name, user.Username),
         new Claim(ClaimTypes.Email, user.Email),
         new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())
     };
     claims.AddRange(user.Roles.Select(role => new Claim(ClaimTypes.Role, role)));

     var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Secret));
     var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

     var token = new JwtSecurityToken(
         issuer: _jwtSettings.Issuer,
         audience: _jwtSettings.Audience,
         claims: claims,
         expires: DateTime.UtcNow.AddHours(_jwtSettings.TokenLifetimeInMinutes),
         signingCredentials: credentials
         );

     return new JwtSecurityTokenHandler().WriteToken(token);
 }

A questo punto possiamo inserire una nuova classe che definisce un attributo per il filtro dei ruoli sugli ending point. Chi volesse approfondire i concetti può seguire questo link.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Security.Claims;

namespace Internal.WebApi.Filters
{
    public class RoleAuthorizeAttribute : Attribute, IAuthorizationFilter
    {
        private readonly string[] _roles;

        public RoleAuthorizeAttribute(params string[] roles)
        {
            _roles = roles;
        }

        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var user = context.HttpContext.User;

            if (!user.Identity.IsAuthenticated)
            {
                context.Result = new UnauthorizedResult();
                return;
            }

            var userRoles = user.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList();
            if (!_roles.Any(role => userRoles.Contains(role)))
            {
                context.Result = new ForbidResult();
            }
        }
    }
}

A questo punto possiamo definire nei controller attraverso questo attributo il filtro sui ruoli.

 [HttpGet]
 [RoleAuthorize("Manager", "Admin")] 
 public async Task<IActionResult> GetAll([FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 10)
 {
     if (pageNumber <= 0 || pageSize <= 0)
         return BadRequest("PageNumber and PageSize must be greater than 0.");

     var result = await _getCustomersWithPaginationUseCase.ExecuteAsync(pageNumber, pageSize);
     return Ok(result);
 }

Abbiamo concluso la parte di registrazione e di gestione dei ruoli. Ci sono dei miglioramenti da fare. Sulla parte di registrazione dobbiamo:

  • Inviare una mail per la conferma dell’account
  • Utilizzare un 2FA

La scaletta definita nell’ultimo articolo era questa:

  • Gestione utenti tramite database MongoDB
  • RBAC admin / user
  • Aggiunta ending point per la gestione CRUD dei progetti
  • Pipeline di pubblicazione
  • K8s su Hetzner

Nel prossimo articolo vedremo come implementare la mail di conferma e il 2FA.

Numero di pomodori impiegati: 3.
Supporto chatGPT.


Pubblicato

in

da

Tag: