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.