Roles are managed using ASP.NET Core Identity’s IdentityRole:
public class IdentityRole{ public string Id { get; set; } // Unique role ID public string Name { get; set; } // Role name (e.g., "Administrator") public string NormalizedName { get; set; } // Uppercase for case-insensitive comparison public string? ConcurrencyStamp { get; set; } // For optimistic concurrency}
var command = new CreateRoleCommand{ Name = "ContentEditor", Description = "Can create and edit content but not publish"};var result = await mediator.Send(command);if (result.Succeeded){ Console.WriteLine($"Role created with ID: {result.Value}");}
API Request Example
cURL
curl -X POST https://api.example.com/api/Roles \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "name": "ContentEditor", "description": "Can create and edit content but not publish" }'
Response:
{ "success": true, "roleId": "role-id-123", "message": "Role created successfully"}
From src/Application/Permissions/Commands/CreatePermission/CreatePermissionCommand.cs:
var command = new CreatePermissionCommand{ Name = "reports.export", Description = "Export report data to CSV/Excel", Module = "Reports"};var result = await mediator.Send(command);
From src/Application/Permissions/Commands/CreatePermission/CreatePermissionCommandValidator.cs:5:
CreatePermissionCommandValidator.cs
public class CreatePermissionCommandValidator : AbstractValidator<CreatePermissionCommand>{ public CreatePermissionCommandValidator() { RuleFor(x => x.Name) .NotEmpty().WithMessage("El nombre del permiso es requerido") .MaximumLength(100).WithMessage("El nombre no puede exceder 100 caracteres") .Matches("^[a-z0-9._-]+$").WithMessage("Use formato: modulo.accion (ej: users.create)"); RuleFor(x => x.Module) .NotEmpty().WithMessage("El módulo es requerido") .MaximumLength(50).WithMessage("El módulo no puede exceder 50 caracteres"); RuleFor(x => x.Description) .MaximumLength(500).WithMessage("La descripción no puede exceder 500 caracteres") .When(x => !string.IsNullOrEmpty(x.Description)); }}
Permission names must be lowercase alphanumeric with dots, underscores, or hyphens only.
var command = new AssignRoleToUserCommand{ UserId = "user-123", RoleId = "manager-role-id"};var result = await mediator.Send(command);
Complete Handler Implementation
AssignRoleToUserCommandHandler.cs
public class AssignRoleToUserCommandHandler : IRequestHandler<AssignRoleToUserCommand, Result>{ private readonly UserManager<ApplicationUser> _userManager; private readonly RoleManager<IdentityRole> _roleManager; public async Task<Result> Handle( AssignRoleToUserCommand request, CancellationToken cancellationToken) { // Find user var user = await _userManager.FindByIdAsync(request.UserId); if (user == null) return Result.Failure(new[] { "User not found" }); // Find role var role = await _roleManager.FindByIdAsync(request.RoleId); if (role == null) return Result.Failure(new[] { "Role not found" }); // Check if already assigned var isInRole = await _userManager.IsInRoleAsync(user, role.Name!); if (isInRole) return Result.Failure(new[] { "User already has this role" }); // Assign role var result = await _userManager.AddToRoleAsync(user, role.Name!); if (!result.Succeeded) { return Result.Failure( result.Errors.Select(e => e.Description).ToArray()); } return Result.Success(); }}
var query = new GetUserRolesQuery { UserId = "user-123" };var roles = await mediator.Send(query);foreach (var role in roles){ Console.WriteLine($"{role.Name}: {role.Description}");}
var createRole = new CreateRoleCommand{ Name = "ContentModerator", Description = "Reviews and approves user-generated content"};var roleResult = await mediator.Send(createRole);var roleId = roleResult.Value;
2
Create permissions
var permissions = new[]{ new CreatePermissionCommand { Name = "content.view", Module = "Content" }, new CreatePermissionCommand { Name = "content.approve", Module = "Content" }, new CreatePermissionCommand { Name = "content.reject", Module = "Content" }};var permissionIds = new List<int>();foreach (var perm in permissions){ var result = await mediator.Send(perm); permissionIds.Add(result.Value);}
Cause: JWT tokens cache permissions; changes require new tokensSolution:
Ask user to log out and log back in
Implement token refresh endpoint
Set shorter token expiration times
Invalidate existing tokens on permission change
Cannot delete role
Cause: Role is assigned to users or has dependenciesSolution:
// Check for users with this rolevar usersInRole = await _userManager.GetUsersInRoleAsync(roleName);if (usersInRole.Any()){ // Reassign users or remove role assignment foreach (var user in usersInRole) { await _userManager.RemoveFromRoleAsync(user, roleName); }}// Now delete the roleawait _roleManager.DeleteAsync(role);