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 ?

Beatles1692
  • 5,214
  • 34
  • 65
  • Have you tackled this? – BennyM Oct 02 '14 at 07:26
  • I should check with our developers but at that time we thought that may be we could play with session timeout. – Beatles1692 Oct 02 '14 at 07:58
  • 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: http://www.asp.net/signalr/overview/guide-to-the-api/handling-connection-lifetime-events – Chris Nov 03 '14 at 11:07
  • 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. – Tyler Durden Jun 09 '15 at 17:09
  • 3
    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: http://abhijitjana.net/2011/01/15/programmatically-changing-session-state-behavior-in-asp-net-4-0/ – Simon Mourier Jan 23 '16 at 10:32
  • Using session with signalr is discouraged look at these posts http://stackoverflow.com/questions/20522477/no-access-to-the-session-information-through-signalr-hub-is-my-design-is-wrong http://stackoverflow.com/questions/7854663/signalr-doesnt-use-session-on-server http://stackoverflow.com/questions/20520874/signalr-how-to-survive-accidental-page-refresh/20521466#20521466 – lyz Jan 25 '16 at 12:53
  • @SimonMourier To me, that is a really great solution. Post it and you'll have my up vote. – Asons Jan 28 '16 at 19:57
  • @LGSon - sa_ddam213 has written a similar answer I believe – Simon Mourier Jan 28 '16 at 21:30
  • @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. – Asons Jan 29 '16 at 07:40
  • @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. – Asons Jan 29 '16 at 07:47
  • @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 so – Simon Mourier Jan 29 '16 at 07:51
  • 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? – Asons Jan 29 '16 at 15:24
  • @LGSon yes we had to support older version of IE. – Beatles1692 Jan 30 '16 at 09:37
  • 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. – Asons Jan 30 '16 at 09:43

4 Answers4

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.

sa_ddam213
  • 42,848
  • 7
  • 101
  • 110
  • Is it just the javascript client that it polling the server? Could you override this with some sort of timer? – ste-fu Jan 28 '16 at 11:53
  • Will this question and answer give you some other ideas on how to avoid sessions renewal and/or detect SignalR requests: http://stackoverflow.com/questions/22002092/context-user-identity-name-is-null-with-signalr-2-x-x-how-to-fix-it – Asons Jan 29 '16 at 15:49
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.

Pawel
  • 31,342
  • 4
  • 73
  • 104
  • 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 next – sa_ddam213 Jan 26 '16 at 05:10
  • 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... – Pawel Jan 26 '16 at 05:54
  • 1
    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 above – sa_ddam213 Jan 26 '16 at 20:09
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
            );
Asons
  • 84,923
  • 12
  • 110
  • 165
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.

yazanpro
  • 4,512
  • 6
  • 44
  • 66
  • 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. – TechSavvySam Feb 08 '18 at 13:43
  • 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. – yazanpro Feb 10 '18 at 02:22