I'm working on an ASP.Net Core 3.1 web API.
The API users are from an Azure AD. The users can access the API if they have a license, each user can be assigned to multiple licenses and the same license can be assigned to multiple users.
The client want me to structure the API routes with a template like <api_url>/{licenseId}/controller/action.
My controllers are like:
[Authorize]
[Route("{licenseId}/Foo")]
public class FooController : ControllerBase
{
If I think of the License as a Resource, I can use the Resource based authorization as detailed here. It works but I find myself copying and pasting the auth check for all the actions.
Is there a better way to authorize an user using the route values?
Here what I've got so far:
public class LicenseRequirement : IAuthorizationRequirement
{
public Guid LicenseId { get; private set; }
public LicenseRequirement(Guid licenseId)
{
LicenseId = licenseId;
}
}
public class LicenseAuthorizationHandler : AuthorizationHandler<LicenseRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<LicenseAuthorizationHandler> _logger;
private readonly DBContext _db;
public LicenseAuthorizationHandler(DBContext context, ILogger<LicenseAuthorizationHandler> logger, IHttpContextAccessor httpContextAccessor)
{
_logger = logger;
_db = context;
_httpContextAccessor = httpContextAccessor;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, LicenseRequirement requirement)
{
var userId = new Guid(context.User.GetUserId());
var licenseId = _httpContextAccessor.HttpContext.GetRouteData().Values["licenseId"];
if (await _db.ApiUsers.SingleOrDefaultAsync(x => x.LicenseId == new Guid(licenseId as string) && x.UserId == userId) is ApiUser user)
context.Succeed(requirement);
}
}
Now I'm a bit stuck as I don't know how to set it up in Startup.cs and use it as an attribute or area filter creating those requirement at runtime with the licenseId route's value.