Reputation: 9085
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.
Upvotes: 10
Views: 9275
Reputation: 412
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.
Upvotes: 0
Reputation: 11
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
context.Fail()
which will correctly exit the middleware pipeline
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();
Upvotes: 1
Reputation: 546
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:
Create a custom Requirement:
public class SomeCustomRequirement : IAuthorizationRequirement
{}
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);
}
}
}
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.
Upvotes: 9
Reputation: 1283
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 :
If /login/home
redirects to a webpage:
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.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.
Upvotes: 1