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();
}
2

There are 2 best solutions below

1
Xinran Shen On BEST ANSWER

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.

3
MGDevTech On

It sounds like you want to perform a custom authorization check in your .NET Core 6 MVC app and, if the user fails the check, redirect them to a "NoAccess" page without executing the controller action. You're on the right track with using an AuthorizationHandler, but you need to handle the redirect correctly.

Here's how you can achieve this:

  1. Create a custom AuthorizationHandler:

    First, create a custom authorization handler that checks your custom authorization logic. In this handler, you can check if the user fails the authorization, and if they do, set a flag indicating that they should be redirected.

    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    
    public class CustomAuthorizationHandler : AuthorizationHandler<CustomAuthorizationRequirement>
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
    
        public CustomAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }
    
        protected override Task HandleRequirementAsync(
            AuthorizationHandlerContext context, 
            CustomAuthorizationRequirement requirement)
        {
            // Your custom authorization logic here
            bool isAuthorized = YourCustomAuthorizationCheckLogic();
    
            if (!isAuthorized)
            {
                // Set a flag to indicate the need for redirection
                _httpContextAccessor.HttpContext.Items["RedirectToNoAccess"] = true;
                context.Fail();
            }
            else
            {
                context.Succeed(requirement);
            }
    
            return Task.CompletedTask;
        }
    
        private bool YourCustomAuthorizationCheckLogic()
        {
            // Implement your custom authorization logic here
            // Return true if the user is authorized; otherwise, return false
            // For example:
            // return SomeAuthorizationLogic();
        }
    }
    
  2. Register the AuthorizationHandler:

    In your Startup.cs or wherever you configure services, register your custom AuthorizationHandler and set up the authorization policy to use it.

    services.AddAuthorization(options =>
    {
        options.AddPolicy("CustomAuthorizationPolicy", policy =>
        {
            policy.Requirements.Add(new CustomAuthorizationRequirement());
            policy.AddAuthenticationSchemes("YourAuthenticationScheme"); // Optional
        });
    });
    
    services.AddSingleton<IAuthorizationHandler, CustomAuthorizationHandler>();
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    
  3. Apply the authorization policy to your controller actions:

    Finally, apply the authorization policy to your controller actions or controllers where you want to perform this custom authorization check.

    [Authorize(Policy = "CustomAuthorizationPolicy")]
    public class YourController : Controller
    {
        // Your controller actions here
    }
    
  4. Create a Middleware to handle redirection:

    To handle the redirection, create a middleware that checks the flag set in the HttpContext by the AuthorizationHandler and performs the redirection if necessary. You can add this middleware in your Startup.cs:

    app.Use(async (context, next) =>
    {
        var redirectToNoAccess = context.Items["RedirectToNoAccess"] as bool? ?? false;
        if (redirectToNoAccess)
        {
            context.Response.Redirect("/NoAccess");
            return;
        }
    
        await next();
    });
    

With this setup, if the user fails the custom authorization check, the CustomAuthorizationHandler will set the "RedirectToNoAccess" flag in the HttpContext. Then, the middleware will detect this flag and perform the redirection without executing the controller action.