Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom redirect from AuthorizationHandler (ASP.NET Core)

I'm using an authentication middleware that is making API requests to a third party service. This middleware then sets up the claims that are later handled by an AuthorizationHandler in conjunction with a IAuthorizationRequirement and a custom policy.

The middleware piece works and I'm able to build the claims:

context.User.AddIdentity(identity); // contains claims

Where I'm stuck is redirecting to a specific URL (there are custom rules for where we need to redirect) from the handler or attribute. From the handler I tried:

var mvcContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
mvcContext.Result = new RedirectToActionResult("login", "home", null);

but it's ignored; only a 401 is returned. AuthorizeAttribute no longer has OnAuthorization so I can't use that either...

Thoughts? Thanks.

like image 867
pbz Avatar asked Jan 17 '17 21:01

pbz


2 Answers

Your approach with the AuthorizationFilterContext in the handler was almost correct. As described in this answer you need to also say context.Succeed(requirement); for it to work.

So complete solution would be something like this:

  1. Create a custom Requirement:

    public class SomeCustomRequirement : IAuthorizationRequirement
    {}
    
  2. Create a custom Handler:

    public class SomeCustomRequirementHandler : AuthorizationHandler<SomeCustomRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserInformationCompletedRequirement requirement)
        {
            if (!fulfillsRequirement())
                if (context.Resource is AuthorizationFilterContext redirectContext)
                    redirectContext.Result = new RedirectResult("/Account/Unauthorized");
                // even though this is weird it is necessary
                context.Succeed(requirement);
            }
        }
    }
    
  3. Register the handler:

    services.AddAuthorization(options =>
    {
        options.AddPolicy(IdentityConstants.UserInformationCompletePolicy, policy =>
        {
            policy.RequireAuthenticatedUser();
            policy.Requirements.Add(new SomeCustomRequirement());
        });
    });
    
    services.AddSingelton<IAuthorizationHandler, SomeCustomRequirementHandler>();    
    // OR transient if the handler uses the Entity Framework
    services.AddTransient<IAuthorizationHandler, SomeCustomRequirementHandler>();
    

Even though my answer is late I hope it might help future visitors.

like image 125
Morosko Avatar answered Oct 26 '22 01:10

Morosko


If the only thing you want to attempt in your API's Middleware is to perform a LogIn behaviour, as your code seems to explain, these possible cases are, in my opinion, thought-provoking :

  1. If /login/home redirects to a webpage:

    • You should use HttpContext.Response.Redirect to redirect to the LogIn webpage. As the documentation says, This would send a 301 code that any web browser can interpret. The HttpContext is available in the Invoke method.
  2. If /login/home redirects to a controller that performs logic that validates user identity:

    • You can instead validate the identity of your user inside the middleware rather than in a separate route. Just like the great Nate Barbettini does.

Take also a look at Nate's post, and this question.

like image 27
Christopher J. Avatar answered Oct 26 '22 01:10

Christopher J.