Custom redirect from AuthorizationHandler (ASP.NET Core)
Asked Answered
I

4

10

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.

Interfertile answered 17/1, 2017 at 21:10 Comment(0)
A
8

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.

Abash answered 25/11, 2018 at 15:57 Comment(3)
if anyone comes here now with .net 6+ this doesn't work. You need: []if (context.Resource is HttpContext httpContext) { httpContext.Response.Redirect("/Errors/Unauthorized"); context.Succeed(requirement); }Confounded
@Confounded You're right, context.Resource was changed to HttpContext. But httpContext.Response.Redirect doesn't work for me. Is it working for you without any additional chagnes?Conklin
I know this is an old one, but I found that returning the context.Succeed with the redirect causes the controller action to run first and then redirect to the unauthorized page which could cause some action code to run which shouldn't. So I'm not sure what the more correct way would be to handle this? Doing context.Fail just gives them a 403 error without doing the redirect. I can't find anything about how to do this properly.Osy
A
1

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:

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

Alfeus answered 16/2, 2017 at 10:44 Comment(0)
K
1

As Brent said, calling context.Succeed(requirement) will not exit the middleware pipeline, so the controller will end up returning a body along with the redirect. This is a pretty big security issue as it can return sensitive content.

A solution my team went with is to

  • call context.Fail() which will correctly exit the middleware pipeline
    • This will set the response code to 401
  • add a middleware above authentication that checks for a 401, and changes it to a redirect.
app.Use(
    async (context, next) =>
    {
        // note: headers might be sent before middleware ends otherwise
        context.Request.EnableBuffering();
        // call the rest of the auth middleware
        await next();
        if (
            context.Response.StatusCode == 401
            && (
                context.Request.Path.StartsWithSegments("/your-route")
            )
        )
        {
            // change the response to a normal redirect.
            // could assert the response body is empty. 
            context.Response.StatusCode = 302;
            context.Response.Redirect("/LogIn");
        }
    }
);
app.UseAuthentication();
app.UseAuthorization();
Keep answered 30/10, 2023 at 17:11 Comment(0)
A
0

this post seems to get closest to a similar problem I am having whereby when my AuthorizationHandler fails to authorize a minimal API endpoint request I get a 302 redirect which creates havoc in my client Angular app that makes the request.

I cannot find any way that I can intercept a request that has failed authorization and based upon the route specified simply switch the response to a 401.

Allot answered 17/6, 2024 at 14:11 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.