Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A controller action which returns a partial view inserts the logon page when authorization fails

Tags:

I have a view that loads a partial view from a controller action using jQuery. The controller action is decorated with the Authorize attribute and if the user has timed out when that action is called intead of being redirected to the proper LogOn page, the LogOn page gets inserted in the view where the partial view would have gone.

There is another post here that describes a solution using jQuery but unfortunately it doesn't include any code samples which is what I need because I'm not very familiar with jQuery (and new to web development) and I need to get this issue resolved as soon as I can.

All I want to do is redirect the user to the proper LogOn page which sounds pretty straight forward to me but so far it doesn't seem to be. If jQuery is the way to go, could someone please post a code sample, that would really help me out.

Thanks.

like image 1000
fynnbob Avatar asked Nov 16 '10 20:11

fynnbob


1 Answers

Solution 1, return response indicating error


Pro ASP.NET MVC 2 Framework offers one solution for this

If an Ajax request is denied authorization, then usually you don’t want to return an HTTP redirection to the login page, because your client-side code is not expecting that and may do something unwanted such as injecting the entire login page into the middle of whatever page the user is on. Instead, you’ll want to send back a more useful signal to the client-side code, perhaps in JSON format, to explain that the request was not authorized. You could implement this as follows:

public class EnhancedAuthorizeAttribute : AuthorizeAttribute  {      protected override void HandleUnauthorizedRequest(AuthorizationContext context)      {          if (context.HttpContext.Request.IsAjaxRequest()) {              UrlHelper urlHelper = new UrlHelper(context.RequestContext);              context.Result = new JsonResult {                  Data = new {                      Error = "NotAuthorized",                      LogOnUrl = urlHelper.Action("LogOn", "Account")                  },                  JsonRequestBehavior = JsonRequestBehavior.AllowGet              };          }          else              base.HandleUnauthorizedRequest(context);      }  }  

You'd then get back a JSON object from the controller {'Error': 'NotAuthorized', 'LogonUrl': '...'} which you could then use to redirect the user.

And alternative response, if you're expecting HTML, could be to return a plain string, like NotAuthorized:<your_url> and check if the response matches this pattern.


Solution 2, return and handle 401 Unautorized status code


As this has to be checked on every ajax request's success callback, this gets quite tedious. It would be nice to be able to catch this case globally. The best solution would be to return a 401 Unauthorized status code from the server and use jQuery's .ajaxError to catch it, but this is problematic on IIS/ASP.NET, since it's deadly committed to redirecting you to the login page if the response has this statuscode. As long as the <authentication> tag is present in web.config this redirection will happen [1] if you don't do something about it.

So let's do something about it. This hacky way seemed nice. In your global.asax.cs

protected void Application_EndRequest() {     if (Context.Response.StatusCode == 302 && Context.Request.RequestContext.HttpContext.Request.IsAjaxRequest())     {         Context.Response.Clear();         Context.Response.StatusCode = 401;     } } 

(I'd love to hear it if anyone knows of any better methods to return a 401 status code.)

By doing this, you're preventing the default behaviour of redirecting to the logon page when the request is an ajax request. You can therefore use the default AuthorizeAttribute as it is, since Application_EndRequest takes care of the rest.

Now, in the jQuery code we'll use the .ajaxError function to catch the error. This is a global ajax event handler, meaning it will intercept every error made by any ajax call. It can be attached to any element - in my example I've just chosen body cause its always present

$("body").ajaxError(function(event, XMLHttpRequest, ajaxOptions, thrownError) {     if (XMLHttpRequest.status == 401) {         alert("unauthorized");     } }); 

This way you get your redirection logic centralized in one place, instead of having to check it on every damn ajax request success callback


Solution 3, return custom header


This answer offers an alternative solution. It uses the same kind of global event handler, but skips the hacky bits. It adds a custom header to the page if you're not authenticated and the request is an ajax request, and checks for this header on each .ajaxComplete. Note that the method provided in the linked answer is unsecure, as it will return the original view with possibly sensitive content and rely on javascript to redirect the user away from it. With a bit of modification it's quite awesome as it doesn't require any hacks and the logic can be centralized to one callback function. The only downside as I can see is that it doens't reply with the semantically correct status code, as it's actually not 200 OK because you're 401 Unauthorized


We can bake this into another custom EnhancedAuthorizationAttribute

public class EnhancedAuthorizeAttribute : AuthorizeAttribute {         protected override void HandleUnauthorizedRequest(AuthorizationContext context)         {             if (context.HttpContext.Request.IsAjaxRequest())              {                 context.HttpContext.Response.AddHeader("REQUIRES_AUTH", "1");                 context.Result = new EmptyResult();             }             else             {                 base.HandleUnauthorizedRequest(context);             }         }     } } 

Now, every time an ajax request completes, you check for this header:

$('body').ajaxComplete(function(event,request,settings){     if (request.getResponseHeader('REQUIRES_AUTH') === '1'){         alert("unauthorized");     }; }); 
like image 90
Simen Echholt Avatar answered Oct 27 '22 02:10

Simen Echholt