Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I redirect to my login screen when unauthorized while also returning a 401 status code in MVC?

Tags:

asp.net-mvc

On our MVC website, we secure our endpoints with the Authorize attribute, however I would like to redirect the user to the login screen when this occurs. My first solution was to redirect on Application_EndRequest in Global.asax.cs:

    protected void Application_EndRequest(object sender, EventArgs e)
    {
        if (Response.StatusCode == (int) HttpStatusCode.Unauthorized)
        {
            Response.ClearContent();
            Response.Redirect("/Account/Login");
        }
    }

The problem with this is that some of our views are loaded by AJAX, in which case we do not want to show the login screen within dynamically-loaded div (this happens for example when the back button is pressed after logging out). It would be better if the request still returned the login screen, but with a 401 Unauthorized status code so that we can detect this event. Obviously this will not work with this approach because the redirection relies on a 302 status code.

Instead, I would rather 'insert' the login screen content inside Application_EndRequest with a 401 status code. So, instead of the Response.Redirect, I tried this:

            ...
            Response.ClearContent();
            Server.TransferRequest("~/Account/Login");
            Response.StatusCode = (int) HttpStatusCode.Unauthorized;
            ...

The redirect works nicely, but because this performs a new request, I can't set the status code in this way, and I get a 200. Still no good for detecting an unauthorized request.

I also tried:

            ...
            Response.ClearContent();
            Server.Transfer("~/Account/Login");
            Response.StatusCode = (int) HttpStatusCode.Unauthorized;
            ...

But I get an exception "Error executing child request for /Account/Login."

I also tried:

            ...
            Response.ClearContent();
            HttpContext.Current.RewritePath("~/Account/Login");
            Response.StatusCode = (int) HttpStatusCode.Unauthorized;
            Response.End();
            ...

But this throws an exception "Thread was being aborted". I'm not sure if the Response.End() is necessary but without it, I just get a standard IIS 401 error.

How can I return the content of my login screen but with a 401 status code?

like image 549
devrobf Avatar asked Aug 19 '16 09:08

devrobf


1 Answers

Sending a 401 AND a ViewObject or RedirectObject is not how the MVC Framework was designed to work: -- a few posts have cleverly shown that returning a 401 with a View witn an authorization filter..

I specify the MVC Framework, but this is not even the prefered way to use the HTTP Protocol.

  • When you redirect in your MVC application, you have created a RedirectResult
  • When your MVC application returns with a call to the Controller's View() method, a ViewResult is created

Whether a Redirect or a View eventually reaches the client browser either,

  • an HTTP Return Code of 200 -- Success will be returned

Or

  • an HTTP Return Code of 302 -- Redirect will be returned

The reason for this convention is that, the server is "handling" the redirect. If the user is not authorized, the server will redirect the user to the login page.

Simlarly, in AJAX or asynchronous HTTP requests, so long as an HTML View is returned, the HTTP request is considered a success. Redirect (302) is not returned for asynchronous requests.


Sending Error Codes to client:

If you want to return a 401 unauthorized to the client, that usually means that you want to the client to "handle" the unauthorized request by navigating to the Login or Error View.

$.ajax({
    url: loginURL,
    data: $("#form").serialize(),
    type: "POST",
    dataType: "html"
})
.success(function (result, status) {
            /*additional logic*/
})
.error(function (xhr, status) {
    if (xhr.status == 401)
    {
        window.location = unauthorizedUrl; //redirect to unauthorized url
    }
});

What if you want to Redirect to Login Page AND Display an Error

your question makes most sense if you want to redirect to a Login Page AND display an Error

Bad Login Attempt

Then, the convention is to Add your error message into Viewbag or a ModelState Error

such as:

        var user = await signInManager.UserManager.FindByNameAsync(model.UserName);

                ModelState.AddModelError("", "Invalid username or password.");
                return View(model);

In your View, to have code that looks for an error:

A ValidationSummary would pickup the Model error

            @Html.ValidationSummary(true)

This specific example of Bad Login Attempt doesn't exactly speak to your example.

I couldn't find an image for a user redirected because they were not authorized, but, in this case, the logic is similar. Error Msg can be populated with a QueryString, Viewbag, etc.

like image 165
Dave Alperovich Avatar answered Oct 25 '22 03:10

Dave Alperovich



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!