How do you handle ajax requests when user is not authenticated?
Someone enters the page, leaves room for an hour, returns, adds comment on the page that goes throuh ajax using jQuery ($.post
). Since he is not authenticated, method return RedirectToRoute result (redirects to login page). What do you do with it? How do you handle it on client side and how do you handle it in controller?
Step 2: Log fatal message in case of error or timeout $. ajax(url, { "data": requestData, "type": "POST", "timeout": 5000 }) . done(function (data, textStatus, jqXHR) { // Process data, as received in data parameter }) . fail(function (jqXHR, textStatus, errorThrown) { // Request failed.
We can use the fail() callback function as well on the JavaScript promise object( the jqXHR object return by the $. ajax() function) to run the specific function on the ajax request fail. function(event, xhr, options) – This is not an optional parameter.
To send a GET request with a Bearer Token authorization header using JavaScript/AJAX, you need to make an HTTP GET request and provide your Bearer Token with the Authorization: Bearer {token} HTTP header.
EDIT:
I wrote above answer a long time ago and now I believe that sending 403 is not proper way to go. 403 has slightly different meaning and it just shouldn't be used. This is corrected attribute using 401. It differs only with additional context.HttpContext.Response.End()
in Http401Result and different HTTP code:
public class OptionalAuthorizeAttribute : AuthorizeAttribute
{
private class Http401Result : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
// Set the response code to 401.
context.HttpContext.Response.StatusCode = 401;
context.HttpContext.Response.Write(CTRes.AuthorizationLostPleaseLogOutAndLogInAgainToContinue);
context.HttpContext.Response.End();
}
}
private readonly bool _authorize;
public OptionalAuthorizeAttribute()
{
_authorize = true;
}
//OptionalAuthorize is turned on on base controller class, so it has to be turned off on some controller.
//That is why parameter is introduced.
public OptionalAuthorizeAttribute(bool authorize)
{
_authorize = authorize;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//When authorize parameter is set to false, not authorization should be performed.
if (!_authorize)
return true;
var result = base.AuthorizeCore(httpContext);
return result;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
{
//Ajax request doesn't return to login page, it just returns 401 error.
filterContext.Result = new Http401Result();
}
else
base.HandleUnauthorizedRequest(filterContext);
}
}
OLD ANSWER:
While I like the ideas posted in other answers (which I had an idea about earlier), I needed code samples. Here they are:
Modified Authorize attribute:
public class OptionalAuthorizeAttribute : AuthorizeAttribute
{
private class Http403Result : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
// Set the response code to 403.
context.HttpContext.Response.StatusCode = 403;
context.HttpContext.Response.Write(CTRes.AuthorizationLostPleaseLogOutAndLogInAgainToContinue);
}
}
private readonly bool _authorize;
public OptionalAuthorizeAttribute()
{
_authorize = true;
}
//OptionalAuthorize is turned on on base controller class, so it has to be turned off on some controller.
//That is why parameter is introduced.
public OptionalAuthorizeAttribute(bool authorize)
{
_authorize = authorize;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//When authorize parameter is set to false, not authorization should be performed.
if (!_authorize)
return true;
var result = base.AuthorizeCore(httpContext);
return result;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
{
//Ajax request doesn't return to login page, it just returns 403 error.
filterContext.Result = new Http403Result();
}
else
base.HandleUnauthorizedRequest(filterContext);
}
}
HandleUnauthorizedRequest
is overridden, so it returns Http403Result
when using Ajax. Http403Result
changes StatusCode to 403 and returns message to the user in response. There is some additional logic in attribute (authorize
parameter), because I turn on [Authorize]
in the base controller and disable it in some pages.
The other important part is global handling of this response on client side. This is what I placed in Site.Master:
<script type="text/javascript">
$(document).ready(
function() {
$("body").ajaxError(
function(e,request) {
if (request.status == 403) {
alert(request.responseText);
window.location = '/Logout';
}
}
);
}
);
</script>
I place a GLOBAL ajax error handler and when ever $.post
fails with a 403 error, the response message is alerted and the user is redirected to logout page. Now I don't have to handle the error in every $.post
request, because it is handled globally.
Why 403, and not 401? 401 is handled internally by MVC framework (that is why redirection to login page is done after failed authorization).
What do you think about it?
The idea I came up with when a coworker asked about how to handle it was this - make an AuthorizeAjax attribute. It can interrogate and verify that Request.IsAjaxRequest() and, if the request isn't authenticated, return a specific JSON error object. It's possible you could simply override the default AuthorizeAttribute and have it call the base unless it's an unauthorized AJAX request so you don't have to worry about whether to tag controller actions with [Authorize] or [AuthorizeAjax].
On the client-side, all your pages would have to be equipped to deal with the returned error, but that logic can likely be shared.
I would propose creating your own AuthorizeAttribute and if the request is an Ajax request, throw an HttpException(401/403). And also switch to use jQuery's Ajax Method instead.
Assuming you've implemented error pages and they return the correct status code, the error
callback will be executed instead of the success
callback. This will be happen because of the response code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With