Handling session timeout in ajax calls

asp.net Mvcasp.net Mvc-3JqueryAuthorize Attribute

asp.net Mvc Problem Overview


I'm making an ajax call using jquery to an asp.net mvc controller action:

[AcceptVerbs(HttpVerbs.Post)]
        public ActionResult GetWeek(string startDay)
        {
            var daysOfWeek = CompanyUtility.GetWeek(User.Company.Id, startDay);
            return Json(daysOfWeek);
        }

When session times out, this call will fail, as the User object is stored in session. I created a custom authorize attribute in order to check if session was lost and redirect to the login page. This works fine for page requests, however it doesn't work for ajax requests, as you can't redirect from an ajax request:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class AuthorizeUserAttribute : AuthorizeAttribute
    {
        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            if (!httpContext.Request.IsAjaxRequest())
            {//validate http request.
                if (!httpContext.Request.IsAuthenticated
                    || httpContext.Session["User"] == null)
                {
                    FormsAuthentication.SignOut();
                    httpContext.Response.Redirect("~/?returnurl=" + httpContext.Request.Url.ToString());
                    return false;
                }
            }
            return true;
        }
    }

I read on another thread that when the user isn't authenticated and you make an ajax request, you should set the status code to 401 (unauthorized) and then check for that in js and redirect them to the login page. However, I can't get this working:

protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (Request.IsAjaxRequest() && (!Request.IsAuthenticated || User == null))
            {
                filterContext.RequestContext.HttpContext.Response.StatusCode = 401;
            }
            else
            {
                base.OnActionExecuting(filterContext);
            }
        }

Basically, it'll set it to 401, but then it'll continue into the controller action and throw an object ref not set to an instance of an object error, which then returns error 500 back to the client-side js. If I change my custom Authorize attribute to validate ajax requests as well and return false for those that aren't authenticated, that makes the ajax request return my login page, which obviously doesn't work.

How do I get this working?

asp.net Mvc Solutions


Solution 1 - asp.net Mvc

You could write a custom [Authorize] attribute which would return JSON instead of throwing a 401 exception in case of unauthorized access which would allow client scripts to handle the scenario gracefully:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            filterContext.Result = new JsonResult
            {
                Data = new 
                { 
                    // put whatever data you want which will be sent
                    // to the client
                    message = "sorry, but you were logged out" 
                },
                JsonRequestBehavior = JsonRequestBehavior.AllowGet
            };
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
}

then decorate your controller/actions with it and on the client:

$.get('@Url.Action("SomeAction")', function (result) {
    if (result.message) {
        alert(result.message);
    } else {
        // do whatever you were doing before with the results
    }
});

Solution 2 - asp.net Mvc

I wouldn't change JsonRequestBehavior to AllowGet. Instead I suggest:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class MyAuthorizeAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);
        OnAuthorizationHelp(filterContext);
    }

    internal void OnAuthorizationHelp(AuthorizationContext filterContext)
    {

        if (filterContext.Result is HttpUnauthorizedResult)
        {
            if (filterContext.HttpContext.Request.IsAjaxRequest())
            {
                filterContext.HttpContext.Response.StatusCode = 401;
                filterContext.HttpContext.Response.End();
            }
        }
    }
}

and add global js ajax errors handler:

   $(document).ajaxError(function (xhr, props) {
        if (props.status === 401) {
            location.reload(); 
        }
   }

Solution 3 - asp.net Mvc

Even though this is well past answered, I think this is the shortest and sweetest answer if you are using .NET 4.5. Little property called SuppressFormsAuthenticationRedirect which was added. Set to true and it will not perform the 302 Redirect to login page.

http://msdn.microsoft.com/en-us/library/system.web.httpresponse.suppressformsauthenticationredirect.aspx

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        // returns a 401 already
        base.HandleUnauthorizedRequest(filterContext);
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            // we simply have to tell mvc not to redirect to login page
            filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
        }
    }
}

Assuming you plan on handling the ajax requests fail/error callback, in which you will get a 401 Unauthorized.

Solution 4 - asp.net Mvc

On Master page add this jquery script ------------

<script type="text/javascript">
    
   $.ajaxSetup({
        statusCode: {
            403: function () {
                window.location.reload();
            }
        }
    });


    OR


    $.ajaxSetup({
        error: function (x, e) {
            if (x.status == 403) {
                window.location.reload(); 
            }
        }
    });

</script>

Add a cs file named with TraceFilter in your project and write a seald class TraceFilterAttribute inheriting to ActionFilterAttribute. Add TraceFilterAttribute class in FilterConfig.cs available in App_Start folder of your project by writing below line.

filters.Add(new TraceFilterAttribute());

Override method OnActionExecuting() in TraceFilterAttribute class. This will automatically check session and if finds session null then calls script available in master page and from their you can go to your choice page.

[AttributeUsage(AttributeTargets.All)]
public sealed class TraceFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
	{
		if (filterContext != null)
		{
HttpSessionStateBase objHttpSessionStateBase = filterContext.HttpContext.Session;
				var userSession = objHttpSessionStateBase["etenetID"];
if (((userSession == null) && (!objHttpSessionStateBase.IsNewSession)) || (objHttpSessionStateBase.IsNewSession))
				{
					objHttpSessionStateBase.RemoveAll();
					objHttpSessionStateBase.Clear();
					objHttpSessionStateBase.Abandon();
					if (filterContext.HttpContext.Request.IsAjaxRequest())
					{
                        filterContext.HttpContext.Response.StatusCode = 403;
						filterContext.Result = new JsonResult { Data = "LogOut" };
					}
					else
					{
						filterContext.Result = new RedirectResult("~/Admin/GoToLogin");
					}
				
				}


}
}

}

Solution 5 - asp.net Mvc

I was having a similar issue and found [this][1]

Instead of returning any JSON, just before the response is sent back, force ASP.NET to return a 401 code. In Global.asax:

protected void Application_EndRequest()
    {
        var context = new HttpContextWrapper(Context);
        if (context.Request.IsAjaxRequest() && context.Response.StatusCode == 302)
        {
            Context.Response.Clear();
            Context.Response.Write("**custom error message**");
            Context.Response.StatusCode = 401;
        }
    }

Then you can let the client deal with it in JavaScript/jQuery or whatever you are using [1]: http://forums.asp.net/t/1823218.aspx/1

Solution 6 - asp.net Mvc

here is how I handle this in so simple way in my custom authorization , I check if session is out and handle this as un-authorized with a boolean to check if it is really authenticated but not authorized (to redirect to un-authorized page) or it is not authenticated due to session time out ( redirect to Login)

 private bool ispha_LoggedIn = false;
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        ispha_LoggedIn = false;
        var session = httpContext.Session;
        bool authorize = false;
        if (httpContext.Session["authenticationInfo"] == null)
        {
        
            return authorize;
        }
       
        using (OrchtechHR_MVCEntities db = new OrchtechHR_MVCEntities())
        {
            UserAuthenticationController UM = new UserAuthenticationController();
            foreach (var roles in userAssignedRoles)
            {
                authorize = UM.IsUserInRole(httpContext.User.Identity.Name, roles);
               
                if (authorize)
                {
                   
                    return authorize;
                }
                    
            }
        }
        ispha_LoggedIn = true;
        return authorize;
    }
    
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (ispha_LoggedIn==false)
        {
            filterContext.Result = new RedirectResult("~/UserAuthentication/LogIn");
        }
        else
        {
            filterContext.Result = new RedirectResult("~/Dashboard/UnAuthorized");
        }
        
      
    }

Hope if this guides someone and please if there're comments its appreciated to know them though.

Solution 7 - asp.net Mvc

You might want to try to throw HttpException and catch it in your javascript.

throw new HttpException(401, "Auth Failed")

Solution 8 - asp.net Mvc

on ajax call if session expired return something like this

<script>
$(function(){
    location.reload();
});
</script>

haha...

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJustinView Question on Stackoverflow
Solution 1 - asp.net MvcDarin DimitrovView Answer on Stackoverflow
Solution 2 - asp.net Mvcfree4rideView Answer on Stackoverflow
Solution 3 - asp.net MvcNicholiView Answer on Stackoverflow
Solution 4 - asp.net MvcShoebView Answer on Stackoverflow
Solution 5 - asp.net MvcShaneAView Answer on Stackoverflow
Solution 6 - asp.net MvcIsPhaView Answer on Stackoverflow
Solution 7 - asp.net MvcDimitriView Answer on Stackoverflow
Solution 8 - asp.net MvcJathin PrasadView Answer on Stackoverflow