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.
As mentioned here Setting Global Authorization policies using defaultpolicy and the fallback policy in aspnet core 3
and here
I used old style global authorization filter for asp.net mvc core, that was ok with asp.net core 2.2, but in 3.0+ its not working as expected when using endpoint routing.
so I changed my code as below: Start.cs:
removed fallback policy from configure services, because I added IpAllowed policy as global requirement as below. removed global authorization filter from options in configure services method and added Require authorization inside configure method inside app.useEndpoints as below:
IpAllowedAuthorizationHandler.cs
updated handler as below: