If your web application is built using ASP.NET MVC stack and it requires user authentication and authorization to access a certain parts of the application (or application as a whole), then the chances are that you using [Authorize] controller attribute. This attribute could be applied to controller as a whole or to any of the controller actions and it acts as a request pre-filter, checking if user is authorized, and if not then directing user to the login page.
Lets consider the following setup:
public class Account: Controller { [HttpGet] public ActionResult Login(string returnUrl) { .... } } [Authorize] public class Home: Controller { [HttpGet] public ActionResult Index() { return View(); } [HttpPost] public ActionResult GetData() { return Json("hello world!"); } }
When browser is hitting /home/index and user is not authorized, then [Authorize] filter is intercepting the request and be returning 302 (temporary redirect) response code:
HTTP/1.1 302 Found Cache-Control: private Content-Type: text/html; charset=utf-8 Location: /Account/Login?ReturnUrl=%2f
If user logged and and then let the session expire then result will be the same, because user is no longer authorized after session is expired. Browser will understand the 302 response and redirect user to the login page.
So, how the GetData action which is returning JSON result for javascript consumption will be handled by [Authorize] attribute? Exactly the same, of course. If user waited for 20 minutes or longer and then performed an action on the page which is resulting in the AJAX call to GetData method then [Authorize] will return 302 code and then Login page as a response body (as HTML).
But it’s not exactly that your javascript code is expecting, most likely. The chances are that your javascript code is expecting a JSON object in return, but in this case it’s not going to happen. Javascript will get a HTML response body instead of the JSON and likely will choke on it.
So, what’s a better way to handle this situation inside javascript? First of all, 302 response is not quite appropriate there. While it’s very friendly for the browser which will use it as a guidance to send user to login page, jQuery (I assume we using $.ajax or $.getJSON methods to call GetData action) will not interpret it automatically. In this case 401 (unauthorized request) seems to be more appropriate. In order to handle this kind of response, let’s create our own version of authorization filter by subclassing the default filter:
Now, we need to modify our client script to handle 401 response code:
This is it. Now we need to decorate our controller with [MyAuthorize] and use the code above for AJAX calls, they’ll be handling session expiration gracefully.
Hello, just a question, I tried the code but the object xhr.Data.LogOnUrl is null, if you see the Response object in the browser it comes empty, the Header is fine with the error 401. Any idea about it ?
Thanks !
Check your forms authentication settings (in web.config). Most likely your LoginUrl is not set. If you using other authentication methods (like windows authentication, for example), or you prefer not to configure LoginUrl, then you can set this url either in HandleUnauthorizedRequest method, or in javascript. Basically, all the you need is to redirect user to the login page somehow.
you must delete the filterContext.HttpContext.Response.End()… it work for me
What’s the problem with filterContext.HttpContext.Response.End()?
Hi, any hint on how to handle this in ASP.Net Core and MVC6?
Thanks!
I get an error because “object xhr.Data is null”, but I don’t know why. Someone can help?
I’m using MVC and I had to modify/add the following code to make this work:
I removed the following line:
filterContext.HttpContext.Response.End();
I added the following to the CookieAuthenticationOptions:
OnApplyRedirect = ctx => {
if (!IsAjaxRequest(ctx.Request)) {
ctx.Response.Redirect(ctx.RedirectUri);
}
}
private static bool IsAjaxRequest(IOwinRequest request)
{
IReadableStringCollection query = request.Query;
if ((query != null) && (query[“X-Requested-With”] == “XMLHttpRequest”)) {
return true;
}
IHeaderDictionary headers = request.Headers;
return ((headers != null) && (headers[“X-Requested-With”] == “XMLHttpRequest”));
}
Hello ,set the statuscode but xhr always showing status=200 and statustext=OK
Hello Stan Tarnovskiy,
i wrote the exact code in AuthorizeAttribute and tried to catch the error both globally and one function only like
$(document).ajaxError(function (e, xhr, opt) {
if (xhr.status === 401) {
window.location.href = xhr.Data.LogOnUrl;
return;
}
});
$.ajax({
type: “POST”,
url: saveurl,
data: { objOrganisation: OrgObj[0] },
dataType: “json”,
success: function (data) {
if (data.errMsg) {
popModal(data.errMsg, “Error”)
}
else {
popModal(“Data Saved Successfully”, “Save”);
$(“#btnclosemsg”).click(function () {
location.reload();
});
$(“#btnclosemsgX”).click(function () {
location.reload();
});
}
},
error: function (xhr) {
if (xhr.status === 401) {
window.location.href = xhr.Data.LogOnUrl;
return;
}
});
but both are not working, always getting xhr.status =200
Can you please explain why this happening and any solution for this???
I am using MVC5-razor
Thanks in Advance