why Authorization Handler invoked two time when added a global authorization filter?

1.9k views Asked by At

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): enter image description here

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.

enter image description here

1

There are 1 answers

6
mesut On BEST ANSWER

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:

 app.UseEndpoints(endpoints =>
{
    endpoints.MapDefaultControllerRoute().RequireAuthorization(Constants.Authorization.IpAllowed);
});

IpAllowedAuthorizationHandler.cs

updated handler as below:

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IpAllowedAuthorizationRequirement requirement)
        {
            if (context.Resource is Microsoft.AspNetCore.Routing.RouteEndpoint endpoint)
            {
                var userIps = ((ClaimsIdentity)context.User.Identity).Claims
                    .Where(c => c.Type == Constants.Authorization.ClaimTypes.IpAddress)
                    .Select(c => c.Value).ToList();
                
                var currentIp = _contextAccessor?.HttpContext.Connection.RemoteIpAddress;
                
                if(currentIp == null)
                {
                    context.Fail();
                    return Task.CompletedTask;
                }

                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
                {
                    context.Fail();
                }
            }

            return Task.CompletedTask;
        }