I used policy-based authorization in asp.net core mvc 3.1, I have an authorization requirement for read, named ReadAuthorizationRequirement, also I have a global IPAllowedAuthorizationRequirement that checks a user's IP and if this IP is allowed succeed the context.
the problem is when I add global authorization filter read requirement invoked two times in first invoke its resource is Microsoft.AspNetCore.Routing.RouteEndpoint in second invoke its resource is Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.AuthorizationFilterContextSealed and this prevent context to authorize successfully.
My Startup.cs is as below:
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
services.AddControllersWithViews(options =>
{
options.Filters.Add(new AuthorizeFilter(policy: Constants.Authorization.IpAllowed));
options.Filters.Add(typeof(Filters.RequestLoggingAttribute));
});
ReadAuthorizationHandler.cs
public class ReadAuthorizationHandler : AuthorizationHandler<ReadAuthorizationRequirement>
{
private readonly IApplicationRouteService _applicationRouteService;
public ReadAuthorizationHandler(IApplicationRouteService applicationRouteService)
{
_applicationRouteService = applicationRouteService;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ReadAuthorizationRequirement requirement)
{
var roles = AuthorizationHanderHelper.FindRole(_applicationRouteService, context, requirement);
if (roles.Any(role => role.Read == true))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
IpAllowedAuthorizationHandler.cs
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IpAllowedAuthorizationRequirement requirement)
{
if (context.Resource is Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext mvcContext)
{
var userIps = ((ClaimsIdentity)context.User.Identity).Claims
.Where(c => c.Type == Constants.Authorization.ClaimTypes.IpAddress)
.Select(c => c.Value).ToList();
var currentIp = mvcContext.HttpContext.Connection.RemoteIpAddress;
var bytes = currentIp.GetAddressBytes();
var badIp = true;
foreach (var address in userIps)
{
if (IPAddress.TryParse(address, out IPAddress testIp))
{
if (testIp.GetAddressBytes().SequenceEqual(bytes))
{
badIp = false;
break;
}
}
}
if (!badIp)
{
context.Succeed(requirement);
}
else
{
var routeData = mvcContext.RouteData;
var action = routeData.Values["action"] as string;
var controller = routeData.Values["controller"] as string;
var area = routeData.DataTokens["area"] as string;
if (string.IsNullOrEmpty(area) && controller == "Account" && action == "AccessDenied")
{
mvcContext.ModelState.TryAddModelError("", $"IP izni verilmemiştir! (IP: {currentIp})");
context.Succeed(requirement);
}
else
{
context.Fail();
}
}
}
return Task.CompletedTask;
}
This is pictures of calls to read on IpAllowed handlers:
1- first call is happened in Read authorization handler and context is endpoint (this call is succeed):
2- second call is happened in Read authorization handler and context is Mvc's AuthorizationFilterContext and this time it fails. in second call IpAllowed is in pending requirements.