Reputation: 69958
I have the System.Web.Mvc.ActionFilterAttribute
below registered as a global filter. If the response is 400 BadRequest
the application works as expected but If I return 401 Unauthorized
what I get is a 302 Found
redirect.
Since the Login?ReturnUrl=
will keep appending values eventually the request will throw a query string is too long exception.
HTTP Error 404.15 - Not Found
The request filtering module is configured to deny a request where the query string is too long.
In Web.config
I have disabled defaultRedirect
and set customErrors
to <customErrors mode="Off" />
public class ClientCertificateActionFilter : System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext actionContext)
{
if (actionContext.HttpContext.Request.Url.Scheme != Uri.UriSchemeHttps)
{
actionContext.Result = new HttpStatusCodeResult(HttpStatusCode.BadRequest, "HTTPS Required");
return;
}
else
{
X509Certificate2 cert = new X509Certificate2(actionContext.HttpContext.Request.ClientCertificate.Certificate);
if (cert == null)
{
actionContext.Result = new HttpStatusCodeResult(HttpStatusCode.Unauthorized, "Client Certificate Required");
return;
}
...
}
}
}
How can I fix this?
Upvotes: 4
Views: 3316
Reputation: 7783
You are returning a HttpStatusCode.Unauthorized
which causes a redirection to the login page. Because the filter is executing each time, it keeps redirecting to itself after it gets there.
What you should return instead is a 403 (HttpStatusCode.Forbidden
).
Upvotes: 2
Reputation: 69958
Update:
I first edited my filter and used AuthorizeAttribute
with AuthorizeCore
. My reason for editing was when I tried something that gave an uncaught exception. In this case the user would be authenticated unless I caught the exception and returned HttpStatusCode.Unauthorized
or something similar. However I had to revert my changed code. The reason for this was that Controllers or Methods declared with [AllowAnonymous]
did not hit the code. The application requires a valid client certificate even to view the login page and therefore I wen't back to ActionFilterAttribute
.
Original:
Looking at the response headers I could see that the response contained the header www-authenticate: Bearer
.
I then wen't to the file App_Start
-> Startup.Auth.cs
to look over the authentication. In OAuthAuthorizationServerOptions
and OAuthBearerAuthenticationOptions
I did not see anything strange. However the application also has app.UseCookieAuthentication
enabled. In here the CookieAuthenticationOptions
was set to LoginPath = new PathString("/Login"),
. This is what was causing the redirects.
Description for LoginPath
:
The LoginPath property informs the middleware that it should change an outgoing 401 Unauthorized status code into a 302 redirection onto the given login path. The current url which generated the 401 is added to the LoginPath as a query string parameter named by the ReturnUrlParameter. Once a request to the LoginPath grants a new SignIn identity, the ReturnUrlParameter value is used to redirect the browser back to the url which caused the original unauthorized status code. If the LoginPath is null or empty, the middleware will not look for 401 Unauthorized status codes, and it will not redirect automatically when a login occurs.
After removing this parameter everything worked.
Upvotes: 1
Reputation: 56849
The issue is that you are extending the wrong part of MVC. MVC has an AuthorizeAttribute
, which is an IAuthorizationFilter
that you can extend to put custom functionality such as this. MVC uses HTTP 401 as a special status that indicates to redirect to the login page, which is what is returned from the default AuthorizeAttribute
(and as far as I know there is no way to change this to use a different status code).
The "solution" you found here only disables the redirect (not just for your special case, but also for AuthorizeAttribute
) so when someone tries to access a page that is not allowed, they will get an HTTP 401 response instead of being redirected to the login page (as most users would expect).
Instead of using an action filter, what you should do is add this extra check to a subclass of AuthorizeAttribute
and then use your custom authorize attribute in place of AuthorizeAttribute
. Then you can keep the login page in place and it will redirect as expected.
public class CustomAuthorize : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
X509Certificate2 cert = new X509Certificate2(httpContext.Request.ClientCertificate.Certificate);
if (cert == null)
{
return false;
}
return base.AuthorizeCore(httpContext);
}
}
If you insist on making this case different than the normal "redirect if not authorized" behavior, you should use a different status code than HTTP 401 for the case of invalid certificate so ASP.NET doesn't hijack the response and turn it into a 302.
Upvotes: 1