Asp.net session never expires when using SignalR and transport mode long polling
Asked Answered
D

4

21

We have a web application that uses SignalR for its notification mechanism.The problem is when we are browsing our web application using IE ,SignalR uses Long Polling as its transport type thus sends back requests to our web server therefore Session never expires no matter how long the browser is idle.

We were thinking that maybe we could catch the requests in Global.asax and see if they were from SingalR and set the session timeout to the remaining time (Which I don't think it's a straightforward solution).

Is there any other solution the we are missing ?

Damnify answered 31/5, 2014 at 14:32 Comment(14)
Have you tackled this?Pimp
I should check with our developers but at that time we thought that may be we could play with session timeout.Damnify
Can you not overwrite the SignalR timeout values or use one of the events to perform the necessary action (i.e kill your session?) - Check this: asp.net/signalr/overview/guide-to-the-api/…Munson
Are you using ASP.NET Identity? I believe ASP.NET differentiates SignalR calls from actual web requests when it comes to session expiration, but I can't comment forsure unless I have some more information about your project, ASP.NET version, and how you handling various things like authentication.Wacky
You can write a specific IHttpModule and add it to the web.config, that would use the Context.SetSessionStateBehavior method to change it to Disabled when the request comes from SignalR, something similar to this: abhijitjana.net/2011/01/15/…Wera
Using session with signalr is discouraged look at these posts #20522977 #7855163 #20521374Tumbling
@SimonMourier To me, that is a really great solution. Post it and you'll have my up vote.Toritorie
@LGSon - sa_ddam213 has written a similar answer I believeWera
@SimonMourier Using an IHTTPModule, yes, but the method you showed, SetSessionStateBehavior, is for me so much cleaner and more appropriate, as it prevents the issue from arising in the first place.Toritorie
@SimonMourier So if you don't do it, I will, as I think it should be an answer with that solution as well, though I will post it as a wiki.Toritorie
@LGson - no problem, do it, as I can't really test it right now :-) I would do it on a different event though, maybe AcquireRequestState or soWera
May I ask which version of IE you refer to using long polling?, as IE10 an above support websocket. And do you really need to support older than IE10?Toritorie
@LGSon yes we had to support older version of IE.Damnify
Yes, that happens sometimes one need to do that. Hopefully you got good answers now, to make life easier for your users. Personally I left the Session object a few years ago, in favor of the Cache object, and have never looked back.Toritorie
B
13

The workaround I am currently using is an IHttpModule to check if the request is a Signalr request, if so remove the authentication cookie, this will prevent the ASP.net session timeout from being reset, so if your Session Timeout is 20min and the only requests are Signalr the users session will still timeout and the user will have to login again.

    public class SignalRCookieBypassModule : IHttpModule
    {
        public void Init(HttpApplication application)
        {
            application.PreSendRequestHeaders += OnPreSendRequestHeaders;
        }

        private bool IsSignalrRequest(string path)
        {
            return path.IndexOf("/signalr/", StringComparison.OrdinalIgnoreCase) > -1;
        }

        protected void OnPreSendRequestHeaders(object sender, EventArgs e)
        {
            var httpContext = ((HttpApplication)sender).Context;
            if (IsSignalrRequest(httpContext.Request.Path))
            {
                // Remove auth cooke to avoid sliding expiration renew
                httpContext.Response.Cookies.Remove(DefaultAuthenticationTypes.ApplicationCookie);
            }
        }

        public void Dispose()
        {
        }
    }

I feel this is a real hack solution so would love so other ideas to prevent session timeout renew when data is pushed to the client from the server, or a when javascript client polls an endpoint for data.

Bronson answered 26/1, 2016 at 5:28 Comment(2)
Is it just the javascript client that it polling the server? Could you override this with some sort of timer?Ramses
Will this question and answer give you some other ideas on how to avoid sessions renewal and/or detect SignalR requests: #22002592Toritorie
N
3

If you take a look at the description of the SignalR protocol I wrote a while ago you will find this:

» ping – pings the server ... Remarks: The ping request is not really a “connection management request”. The sole purpose of this request is to keep the ASP.NET session alive. It is only sent by the the JavaScript client.

So, I guess the ping request is doing its job.

Nealneala answered 25/1, 2016 at 21:1 Comment(3)
The issue I face it I don't want it to keep the session alive, I want the Session timeout to log the user out, not keep them logged in forever, I am handling this in a IHTTP module at the moment and removing the authentication cookie from the response, but that's a bit hacky IMO, Removing the ping fixes that issue, however any push data from the server keeps the session alive also which is the main issue we are facing, I was hoping signalr would work with the ASP session timeouts, as this is a he security hole for us, moving back to raw websockets is where we are heading nextBronson
I am not sure if removing ping requests would change anything - when using long polling SignalR sends a poll request every 110 seconds by default and I think these requests would extend the session too...Nealneala
I DONT want it to extend the session, this is the whole point. Telling me what its already doing is not an answer, the answer would be to stop it from extending the session timeout, like my example aboveBronson
B
2

I here post @Simon Mourier's commented solution, with his approval, as a CW answer, as I find the suggested approach the most appropriate and less intrusive, as it just disables the Session for SignalR requests.

A positive side effect is that the request will be processed faster as the Session object doesn't need to be initiated and loaded.

It still uses a IHttpModule for the work, and the preferable place is likely the AcquireRequestState event (not personally tested yet though), or at an event raised earlier, before making use of the Session object.

Do note using this approach that one might need to test that the Session object is available before access any of its members or stored objects.

public class SignalRSessionBypassModule : IHttpModule
{
    public void Init(HttpApplication application)
    {
        application.AcquireRequestState += OnAcquireRequestState;
    }

    private bool IsSignalrRequest(string path)
    {
        return path.IndexOf("/signalr/", StringComparison.OrdinalIgnoreCase) > -1;
    }

    protected void AcquireRequestState(object sender, EventArgs e)
    {
        var httpContext = ((HttpApplication)sender).Context;
        if (IsSignalrRequest(httpContext.Request.Path))
        {
            // Run request with Session disabled
            httpContext.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Disabled);
        }
    }

    public void Dispose()
    {
    }
}

Here is another completely different approach, simple, yet quite efficient.

Instead of relying on Session/Auth cookies to decide whether a user has timed out, use the Cache object. This have more or less no side effects and work just like if the user simply logged out.

By simply add this small snippet somewhere in the beginning of your web app code, where of course SignalR don't go, you will be able to check if the cache item is there and reinitiate it (with the same expiration time as the Session timeout is set), and if not, just do a logout and remove cookies/session variables.

if (Request.IsAuthenticated) {

  if (Cache[Context.User.Identity.Name] == null) {

    // Call you logout method here...,
    // or just:
    // - Sign out from auth;
    // - Delete auth cookie
    // - Remove all session vars

  } else {

    // Reinitiate the cache item
    Cache.Insert(Context.User.Identity.Name,
                 "a to you usable value",
                 null,
                 DateTime.Now.AddMinutes(Session.Timeout),
                 Cache.NoSlidingExpiration,
                 CacheItemPriority.Default,
                 null
                );
}

And within your user login method, you just add this, to create the cache item for the first time

// Insert the cache item
Cache.Insert(Context.User.Identity.Name,
             "a to you usable value",
             null,
             DateTime.Now.AddMinutes(Session.Timeout),
             Cache.NoSlidingExpiration,
             CacheItemPriority.Default,
             null
            );
Brae answered 31/5, 2014 at 14:32 Comment(0)
C
0

It's more stable and maintainable -in my view- to have your own "session like timeout" . Set your .NET session timeout to infinity since you'll not be using it and then create a global JavaScript counter (in your layout or master page) to track the time passing while the browser is idle (obviously setTimeout or setInterval every few seconds would do the trick). Make sure to have the counter reset on every web request (that should happen automatically since all JavaScript variables would reset). In case you have pages that depend on web services or Web API, make sure to reset your global JavaScript counter on every call. If the counter reaches your desired timeout without being reset, that means that the session is expired and you can logout the user. With this approach you'll have full control over the session lifetime which enables you to create a logout timer popup to warn the user that the session is about to expire. SignalR would perfectly fit with this approach since the JavaScript timer would keep ticking.

Cut answered 29/1, 2016 at 6:50 Comment(2)
I've played around with this option and I additionally used a mouse move event to reset the counter in case the user was interacting on the page but not hitting the server. One issue I found while testing this out is when you have a mobile device and you hide the browser, I think the JavaScript events quit firing. Also since JavaScript is client side and thus "more hackable" the server should really be the ultimate arbiter of session expiration.Ninth
As far as I can tell, hacking JavaScript in this scenario is kinda useless There's nothing you can do for someone who uses a tool to keep moving the mouse on the screen either. As far as hiding the browser on mobile devices, I guess Google did it in their 'timer" function.Cut

© 2022 - 2024 — McMap. All rights reserved.