I'm working with ASP.NET Core 8 and Keycloak (version 24) for authentication and role-based access control.
Scenario: I have a base controller that implements basic CRUD operations for multiple object types, e.g.:
public class BaseController<T> : Controller
{
[HttpGet]
public virtual async Task<IActionResult> Get()
{
// Base implementation
}
// Other CRUD methods
}
For each specific object type, I have a derived controller that only provides the CRUD operations for that specific type, e.g.:
public class ObjectAController : BaseController<ObjectA>
{
// Inherits CRUD operations from BaseController
}
Now, I’ve introduced Keycloak roles like Read_ObjectA, Read_ObjectB, Write_ObjectA, etc.
Problem: I need to enforce authorization for each object type with its respective role. For example, ObjectAController should allow access only to users with the Read_ObjectA or Write_ObjectA roles. However, I want to avoid duplicating the methods in each derived controller just to apply the [Authorize] attribute.
Question: Is there a way to apply authorization generically in the base controller so that I don't have to duplicate methods in each derived controller just to specify the correct role? Would writing a custom authorization service be a better solution here, and if so, how could I implement this cleanly?
A simple but tedious approach would be something like this in the derived controller:
[Authorize(Roles = "Read_ObjectA")]
[HttpGet]
public override async Task<IActionResult> Get()
{
return await base.Get();
}
This feels redundant since I'm only adding the [Authorize] attribute in each derived controller, while the logic remains in the base controller.
I think you just need implement CustomAuthorizeAttribute
public class CustomAuthorizeAttribute : Attribute, IAuthorizationFilter
{
private readonly string[] _roles;
public CustomAuthorizeAttribute(params string[] roles)
{
_roles = roles;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
// Get the controller name
var controllerName = context.RouteData.Values["controller"]?.ToString();
var user = context.HttpContext.User;
var userRoles = user.Claims.Where(c => c.Type == "role").Select(c => c.Value);
// Recombine the "Read" to "Read_ObjectA"
if (!_roles.Any(role => userRoles.Contains(role+"_"+ controllerName)))
{
context.Result = new ForbidResult(); // User is not authorized
}
}
}
Then Use like
[CustomAuthorize("Read")]
//[CustomAuthorize("Read","Write")]
[HttpGet]
public virtual async Task<IActionResult> Get()
{
// Base implementation
}