Handling session timeout in ajax calls
Asked Answered
D

8

57

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?

Duchy answered 8/3, 2011 at 21:59 Comment(0)
K
86

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
    }
});
Keri answered 9/3, 2011 at 7:43 Comment(6)
But that means all client-side code to make ajax calls (whether using jQuery or via Ajax.ActionLink, or Ajax.BeginForm) should count for that. This can work for a small project, but as the site grows, maintaining this would end up with a nightmare.Brianbriana
Mosh, most client frameworks such as jQuery have global error handling capability. jQuery for instance has .ajaxError() which is triggered whenever an Ajax request completes with an error. This makes it pretty easy to handle errors on the client side, even for hte largest projects.Daugherty
Great. But this jQuery logic i need to do it in all the places whereever i call ajax. Is there one global place in jQuery to do this?Badenpowell
@Murali, of course, you could register a global .ajaxComplete() handler.Keri
@DarinDimitrov I created a custom class as you mentioned above and decorate Controller. Then render partialview using AJAX. But whenever I render, the code hits MyAuthorizeAttribute and it returns "sorry, but you were logged out" message. Is there something I missed?Kathie
Can you please take a look what is wrong with my try on follow question? stackoverflow.com/q/53790824/2798643Holston
T
38

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(); 
        }
   }
Tonsillitis answered 2/6, 2012 at 22:14 Comment(4)
For me this works better as I am using a ui framework that I can't totally put in all these checks. Have a global client side catch all works for me much betterReeher
Can you please take a look what is wrong with my try on follow question? stackoverflow.com/q/53790824/2798643Holston
Is there any way to run that base.OnAuthorization method in Core 3.1?Missilery
filterContext is always NULL for me...Cythera
C
10

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.

Cowley answered 11/5, 2013 at 2:22 Comment(0)
C
7

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");
                    }

                }


}
}

}
Contravene answered 1/7, 2013 at 11:46 Comment(3)
Here master page script get fired and reload the current page whenever it gets session expired. When page gets reload then checks session and redirects user to login page for log in.Contravene
Mostly use jquery script written first ie. $.ajaxSetup({ statusCode: { 403: function () { window.location.reload(); } } }); It works fine.Contravene
its' working for me, great work thanks bro i just copied your ajaxSetup code, I have written c# code in a diff way. I just need to handle at client side, you helped me, thanks a lot :)Microphysics
N
4

I was having a similar issue and found this

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

Noh answered 15/12, 2012 at 18:37 Comment(0)
R
1

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.

Radiotelegram answered 7/5, 2016 at 14:55 Comment(0)
S
0

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

throw new HttpException(401, "Auth Failed")
Suburban answered 8/3, 2011 at 22:11 Comment(2)
You're never gonna catch a 401 exception in javascript as the Forms Authentication module will catch it much before javascript executes and simply return a 302 redirect status code to the login page and the AJAX call will simply follow this redirect and get a 200 status code eventually and it would execute the success callback.Keri
@Darin Dimitrov - do you have any ideas what the correct way to handle this situation is?Duchy
T
-3

on ajax call if session expired return something like this

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

haha...

Telecommunication answered 31/3, 2016 at 16:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.