Search code examples
c#.netasp.net-coreasp.net-authorization

ASP.NET Core 6 Redirecting user from AuthorizationHandler, HandleRequirementAsync method without allowing controller action code to run


I have a .net core 6 MVC app where I'm trying to redirect if a user fails a custom AuthorizationHandler check. It redirects them fine, but I found by stepping through, it actually runs the controller action code after the authorization check before redirecting the user to the NoAccess page. This could definitely cause some code to be run that this user shouldn't be able to run, and I think it's because it's setting the redirect but returning context.Succeed so it's saying it's ok to run before the redirect. I tried to return context.Fail but that just gives a hard 403 error without redirecting them. I'm not sure the proper way to handle this? I thought maybe something like adding something to the Program.cs for builder.Services.AddAuthorization for a no access redirect but couldn't find anything on it. So what is the proper way to do this? Here is what I have so far:

    public class ItAdminAuthorizationHandler : AuthorizationHandler<ItAdminRequirement>
{
    private readonly PulseContext _context;
    public ItAdminAuthorizationHandler(PulseContext context)
    {
        _context = context;
    }
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ItAdminRequirement requirement)
    {
        if (context.User == null)
        {
            return Task.CompletedTask;
        }

        var userName = context.User.Identity.Name?;
        //Check the current user is in the user list.
        if (_context.Users.Any(u => u.Username.Equals(userName) && u.Active && u.UserRole.Name == "IT Admin"))
        {
            context.Succeed(requirement);
        }
        else
        {
            if (context.Resource is HttpContext httpContext)
            {
                httpContext.Response.Redirect("/Home/NoAccess");
                context.Succeed(requirement);
                //seems like should set context.Fail() here, but it doesn't do the redirect then.
            }
        }

        return Task.CompletedTask;
    }

and then the controller:

[Authorize(Policy = "ItAdmin")]
public IActionResult Submit()
{
    //Some code that should only run if authorized.
    //but setting context.Succeed like above allows this code to run first and then does the redirect afterwards. I don't want to have to add code to double check to every controller action.
    
    return View();
}

Solution

  • In my opinion, You don't need to redirect in ItAdminAuthorizationHandler, When isAuthorized is false, use context.Fail(); directly to return 403 statecode, Then create an middleware to catch this 403 response and do redirect in this middleware.

    public class CustomMiddleware
        {
            private readonly RequestDelegate _next;
    
            public CustomMiddleware(RequestDelegate next)
            {
                _next = next;
            }
    
            public async Task InvokeAsync(HttpContext httpContext)
            {
         
                await _next(httpContext);
    
                if (httpContext.Response.StatusCode == 403)
                {
                    // Handle the 403 error here
                    httpContext.Response.Redirect("/xxxxx");
                }
            }
        }
    

    Then register this middleware in the top of the request pipline.