Improved Session Tracking
September 22, 2006
I recently discovered a better way to handle session tracking in web applications while dealing with complaints from the users of our application about session interference problems. Session tracking refers to the process of reassociating session data stored on the server with an incoming HTTP request. I will give an overview of existing session tracking strategies and explain their drawbacks. Following that discussion, I will present my solution.
A web container can use a couple of methods to associate an HTTP session with a sequence of user requests, all of which involve passing an identifier between the client and the server. Java servers typically use a token named JSESSIONID, while PHP uses PHPSESSID. The identifier can be maintained on the client as a cookie (known as cookie-based sessions) or the web container can include the identifier in every URL that is returned to the client (known as URL rewriting or encoding). Cookie-based sessions are particularly problematic with the Mozilla line of browsers since cookies which are set to expire at the end of the browsing session (session-scoped cookies) are shared amongst all windows and tabs for a single user profile. Internet Explorer (IE) segregates each window as a different browser session. Since IE doesn't have tabs to consider (at least prior to IE7), it doesn't have this isolation problem with session-scoped cookies. For browsers that do share session-scoped cookies, it is not possible to have multiple HTTP sessions without them interfering with each other.
When the application uses cookie-based sessions, depending on how session data is used, the user may see unexpected behavior when attempting to establish a new session in a separate window (or tab). A sample use case would be when the user wants to create a new session using different login credentials. If the application does not check for the presence of the existing session when the user tries to establish this new session, instead allowing the normal authentication logic to execute, the user may end up with a schizophrenic user principal that has a cocktail of roles and user information. Another approach the application could take is to automatically terminate the outstanding session, which leaves the original window (or tab) in an expired state, only to be discovered the next time the user activates a link or form in that window (or tab). In this case, the two tabs are stealing sessions from one another. The last method the application could take in handling the request is to restrict access to the login page if there is an outstanding session, preventing the user from establishing a second login all together. While this third scenario is the safest bet, none of these options are ideal for the user.
The alternative to cookie-based sessions is URL rewriting. In this strategy, every link or form is processed by the application, which inserts a session token into the URL, either as a query string parameter (PHP) or as a proprietary URL syntax (Java). Multiple tabs and windows no longer pose a problem with concurrent sessions because all the information necessary to reestablish the HTTP session during subsequent requests is stored in the URL, and hence completely isolated. If a request is made using a URL without this token, the application creates a brand new HTTP session and rewrites all the URLs sent in the response with this new identifier. While this strategy may sound like a sure winner over cookie-based session tracking, it has one rather severe security flaw. Since the information that identifies the user's session is now part of the URL, the session is completely portable. The URL can be copied from the browser location bar into and e-mail and sent over the internet to a recipient, who can then copy that URL into a browser window and access the session. You don't have to be a security expert to realize the risk in this scenario. This example doesn't even consider the less obvious risk of picking off session tokens in the URL sent over an insecure network with a traffic sniffer. Using this strategy out of the box is not recommended for anything other than development.
Fortunately, there is a third approach that would allow the use of URL rewriting in a secure manner. (To ensure maximum security in general, SSL should always be used). The trick is to lock a session to its originator. However, none of the request headers sent by the browser can satisfy the requirement of distinguishing one browser from all others. The most logical candidate, the remote IP address, does not work since it can be shared by users behind a firewall or proxy. After thinking about the Urchin Tracking Module (UTM) used by Google Analytics, it finally dawned on me that it is possible to uniquely identify a browser by using a cookie to assign a differentiating visitor identifier. This concept is similar to a MAC address for an ethernet card. The very first time that the application's URL is requested by a browser, interceptor code (for instance a servlet filter) calculates a long random string (the more random the better) and assigns this value to a permanent cookie in the browser. To make the cookie permanent, set it to expire several years in the future. The interceptor code also places this token into the newly created session to signify its originator. In Java, this is done by consulting the isNew() method on the HttpSession object and only binding the originator token to the session if this flag is true. On all subsequent requests for that session identifier, the value in the cookie is compared to the originator token in the session. If the two differ, the user is redirected appropriately, either to an error page or by stripping the session identifier from the URL, forcing the user to create a new session and thus login once again.
Even though a cookie is used in this strategy, it is a permanent one, and it is intended to be shared between tabs and windows. If you look at your browser's cookies, you will see that Google Statistics uses this approach to uniquely identify your browser so that it may track unique page hits with a cookie named __utma.
The proposed solution does, of course, require the use of cookies. If your application employs the URL rewriting strategy in order to avoid the use of cookies, this solution isn't going to work for you. As discussed above, such an approach is dangerous since it allows the URLs to be portable. Granted, when using SSL, the URL itself is ciphered, so at least it cannot be captured while going over the wire. From the outside, the only data that is visible to the world is the hostname and port. Cookies can also be marked as secure so that they are obscured as well.
As a final note, it may be necessary to ensure that the JSESSIONID cookie is removed from the client in order for the servlet container to prefer the URL rewriting approach.
23 Comments from the Peanut Gallery
1 | Posted by Alaa Halasa on December 11, 2006 at 05:06 AM EST
Thanks alot for the nice discussion
2 | Posted by Jacky on January 22, 2007 at 01:29 PM EST
Is there any source code(java) for this discussion?
How can u force to create a new session?
3 | Posted by rich on February 13, 2007 at 09:44 AM EST
hmm.. can i terminate session in one thread, that created many ie activeX copires?
4 | Posted by kghastie on April 20, 2007 at 10:34 AM EST
If you are using a filter anyway (with the third solution), why not just use the filter to encode the URLs? You could hopefully do it only for people who don't have cookies enabled.
5 | Posted by Glen on June 07, 2007 at 08:43 AM EST
You neglected to mention local shared objects - LSO - or flash cookies and also the potential to store a session in an SQL back end. There is also of course an extreme limitation with URL Mangling - per your mention of URL rewriting, being mainly that it has no method of persistence outside of the immediate http context. Each http request being a unique thread within the scope of the application service (.net - java - php - cfusion et el) and can only be persisted by a state storage mechanism, such as a cookie or LSO which can be persisted and recalled based upon extended criteria (mac/ip/client specifics) for recall from state storage mechanisms such as an SQL state service.
Urchin is such a mechanism and is also proprietary Google technology. It is entirely possible to build such a mechanism with relatively few lines of code, however I would say that this article is not exactly clear in the purpose of your content, in describing how deep linking, url mangling or any other form of http rewriting is really going to achieve anything other than a nice way of directing people through a web site. To make it clear you have not covered enough ground on any of the subjects to be specific or have you succeeded in demonstrating that you are able to extend the modal session into a state-full process with any ability for recall, thus making your implied term of "session tracking" incomplete and quite inaccurate.
6 | Posted by Dan Allen on June 11, 2007 at 06:46 PM EST
Um, yeah. a little nit picky, wouldn't you say? This is a blog entry after all, not a published journal article.
In this entry I discuss "tracking a session" to mean that the Java application server is keeping track of which client owns a session and delivering said session to client. The rest of the session handling mechanism in Java is left up to the application server (or servlet container). I'm not trying to reinvent the wheel here. Take or leave me idea, I don't really care.
7 | Posted by Achpee on September 09, 2007 at 11:21 AM EST
Even if you prefer not to say reinventing the wheel - Storing permanent cookie is also not legal for many of the businesses !!
8 | Posted by stocke on September 11, 2007 at 05:48 AM EST
What about when the user follows a link and opens this in a new tab? Then the new tab is in the same session with the same request id. So there is a new tab window but on the server you do not know about this new tab window.
9 | Posted by bacathey on September 13, 2007 at 09:52 AM EST
I think Dan is missing the point of this post. Unfortunately, the server is blissfully unaware about tabs, or new windows for that matter. Hopefully, a field like tabID will become standard in new protocols, especially since IE and FF now support tabs. Having tabs in IE will definitely cause more sites to experience problems related to this. There should be an easy way to resolve it. Stick it in the header!
10 | Posted by Dan Allen on September 14, 2007 at 04:06 AM EST
In the case that you cite, I would think it is desirable to maintain the same session. A link clicked from the application opening in a new tab should still be considered the same session. It is then up to the application to intelligently manage the two tabs (I recommend Seam).
You could, of course, use JavaScript to capture the links that were clicked (as opposed to copied into a new tab, which is effectively what the browser is doing) and then append the session id at that time to maintain the session within the same tab.
Once again, the solution I proposed solves a requirement that we had for our application. Your requirements may be different, so you will need to refine the assumptions that were made to suit your needs.
11 | Posted by Dan Allen on September 14, 2007 at 04:16 AM EST
I want to clarify the requirements I was given when I wrote this code, so that you can better understand the assumptions I made. The requirement was that one user could not e-mail a URL to another user and allow the recipient to start using the "live" session. Hence the "session tracking" part. This situation is very dangerous from a security perspective. At the same time, the user needed to be able to work with two different logins in the same browser (different tabs or windows). That was the motivation for using URL rewriting rather than cookie-based sessions. (Cookie-based sessions are shared across tabs in browsers that behave this way, such as Firefox. You can login again and potentially have a mixed session situation.)
What I did was turn cookie-based session upside down by putting the session id in the URL string and a browser signature token in the cookie. That way, each user had a signature, but the session id was not shared by different tabs that started new sessions. (If the user spawn a tab within the same session, then that would obviously keep the same session active in two tabs.)
The focus was on flexibility without jeopardizing security. Being able to track separate tabs is very interesting, but unrelated to this implementation.
12 | Posted by Dan Allen on September 14, 2007 at 04:22 AM EST
I apologize to my loyal readers for leaving you hanging. I didn't realize that I never posted the code. I didn't have much time to make the page nice, but at least it is up.
SessionTrackingFilter source code
13 | Posted by Henryk Gerlach on December 20, 2007 at 02:52 PM EST
"Even when using SSL, the URL itself is not ciphered, so it can be captured while going over the wire."
The above statement is false for a correct implimentation of SSL. Yet it is still bad practice to put sensitive information into the url, because it might leak by copy&paste or a dropped 'S' in httpS.
See http://answers.google.com/answers/threadview?id=758002
14 | Posted by Jonathan on June 12, 2008 at 05:09 AM EST
It's a bit late... but how does using a permanent cookie solve the problem of session interference? If I logged into the web-application, then opened a new tab for the same web-application, the same cookie will also be sent. The new tab would now be tracked with same session as the first tab
15 | Posted by Dan Allen on June 12, 2008 at 11:47 AM EST
@Jonathan, the purpose of the cookie is to validate that the session requested by the JSESSIONID in the URL was in fact created by the current browser. The same browser could be the originator of any number of sessions that are active in different tabs. But what this cookie prevents is a session created by a different browser to be restored by the current browser.
16 | Posted by JohnE on October 29, 2008 at 11:31 PM EST
Liked what you were mentioning Glen of post #5, but it would have been even more constructive if you mentioned code or resources that could have helped us.
17 | Posted by Tony on November 27, 2008 at 03:12 AM EST
Ok, so this doesn't solve the problem of session sharing - it solves the problem of email urls to others (not something I suspect many of us are trying to solve). You opening paragraph states: "complaints from the users of our application about session interference" - this IS a problem many are trying to solve and, to be blunt, you didn't solve it. - Fess-up.
18 | Posted by Dan Allen on November 27, 2008 at 12:09 PM EST
If you want to be logged as different users simultaneously, then what you want is to use URL rewriting as I suggest in the entry and ensure that each tab starts cleanly (without carrying over the jsessionid). My solution does solve this problem.
Btw, session interference between tabs when logged in as the same user is solved by Seam's conversations.
19 | Posted by vaibhav k Arya on March 24, 2009 at 08:08 AM EST
this has been a very good discussion. And the tips are quite useful. Still I'm confused about URL-Rewriting, I think the entire application will need to be modified if url rewriting is implemented.
20 | Posted by Nico de Wet on July 15, 2010 at 06:48 AM EST
Thanks for the post, serves as a good refresher on these topics. Thanks Dan.
You mentioned JBoss Seam in #10, the Seam documentation does not seem to delve into the lower level implementation details, but Seam appears to use a JSESSIONID cookie.
21 | Posted by Dan Allen on July 15, 2010 at 03:24 PM EST
@Nico, Seam does not deal with how the session is propagated, that is something handled at the servlet container level. Prior to Servlet 3.0, this was done using a vendor-specific setting, such as Tomcat's context.xml. In Servlet 3.0, there is finally a standard flag in web.xml for controlling how sessions are passed on. In your case, you should consult the Tomcat docs [1].
[1] context.xml root attributes
22 | Posted by Thomas Zimmermann on August 23, 2013 at 05:14 PM EST
So the problem is that the JSESSIONID cookie is shared between browser tabs, because the name is not unique for every tab.
So what is needed is a way to identify browser tabs.
I propose a hybrid approach:
Generate a somewhat unique id on every login (timestamp should suffice). This is the name of the Session-Cookie (previously known as JSESSIONID). Set the cookie with this unique name, the value is the standard JSESSIONID as before. Now rewrite every URL to include the unique cookie name.
The server now only needs to reverse this layer of indirection: get the cookie-name from the url and access the cookie with this name.
Urls are now securely emailable, since the id included is not the session identifier, but only a meaningless id. If this id clashes with the cookie name of the recipient, there is no harm done, he will only be logged in with the cookie he already has.
Maybe there are some disadvantages for load balancers expecting a uniform session-id, I don't know.
23 | Posted by Dan Allen on August 23, 2013 at 09:02 PM EST
Thomas,
I strongly encourage you to post these ideas either the DeltaSpike list (CDI extension modules for Java EE) or Rewrite. This could have an important impact on development and get wider exposure if it proves to be viable.
DeltaSpike: http://deltaspike.apache.org/
Rewrite: http://ocpsoft.org/rewrite/