Vision

asp

The Truth about Sessions

by admin on Dec.24, 2008, under asp

Nearly every PHP application uses sessions. This article takes a detailed look at implementing a secure session management mechanism with PHP. Following a fundamental introduction to HTTP, the challenge of maintaining state, and the basic operation of cookies, I will step through simple and effective methods that can be used to increase the security and reliability of your stateful PHP applications.It is a common misconception that PHP provides a certain level of security with its native session management features. On the contrary, PHP simply provides a convenient mechanism. It is up to the developer to provide the complete solution, and as you will see, there is no one solution that is best for everyone.

Statelessness

HTTP is a stateless protocol. This is because there is nothing within the protocol that requires the browser to identify itself during each request, and there is also no established connection between the browser and the web server that persists from one page to the next. When a user visits a web site, the user’s browser sends an HTTP request to the web server, which in turn sends an HTTP response in reply. This is the extent of the communication, and it represents a complete HTTP transaction.

Because the web relies on HTTP for communication, maintaining state in a web application can be particularly challenging for developers. Cookies are an extension of HTTP that were introduced to help provide stateful HTTP transactions, but privacy concerns have prompted many users to disable support for cookies. State information can be passed in the URL, but accidental disclosure of this information poses serious security risks. In fact, the very nature of maintaining state requires that the client identify itself, yet the security-conscious among us know that we should never trust information sent by the client.

Despite all of this, there are elegant solutions to the problem of maintaining state. There is no perfect solution, of course, nor is there one solution that can satisfy everyone’s needs. This article introduces some techniques that can reliably provide statefulness as well as defend against session-based attacks such as session hijacking. Along the way, you will learn how cookies really work, what PHP sessions do, and what is required to hijack a session.

HTTP Overview

In order to appreciate the challenge of maintaining state as well as choose the best solution for your needs, it is important to understand a little bit about the underlying architecture of the web, the Hypertext Transfer Protocol (HTTP).

A visit to http://example.org/ requires the web browser to send an HTTP request to example.org on port 80. The syntax of the request is something like the following:

Toggle Code View

  1. GET / HTTP/1.1
  2. Host: example.org

The first line is called the request line, and the second parameter (a slash in this example) is the path to the resource being requested. The slash represents the document root; the web server translates the document root to a specific path in the filesystem. Apache users might be familiar with setting this path with the DocumentRoot directive. If http://example.org/path/to/script.php is requested, the path to the resource given in the request is /path/to/script.php. If the document root is defined to be /usr/local/apache/htdocs, the complete path to the resource that the web server uses is /usr/local/apache/htdocs/path/to/script.php.

There are many technologies such as mod_rewrite that offer more flexibility when mapping a URL to a particular resource.

The second line illustrates the syntax of an HTTP header. The header in this example is Host, and it identifies the domain name of the host from which the browser intends to be requesting a resource. This header is required by HTTP/1.1 and helps to provide a mechanism to support virtual hosting, multiple domains being served by a single public IP address. There are many other optional headers that can be included in the request, and you may be familiar with referencing these in your PHP code; examples include $_SERVER['HTTP_REFERER'] to refer to the Referer header and $_SERVER['HTTP_USER_AGENT'] to refer to the User-Agent header.

Of particular note in this example request is that there is nothing within it that can be used to uniquely identify the client. Some developers resort to information gathered from TCP/IP (such as the IP address) for unique identification, but this approach has many problems. Most notably, a single user can potentially use a different IP address for each request (as is the case with large ISPs such as AOL), and multiple users can potentially use the same IP address (as is the case in many computer labs using an HTTP proxy). These situations can cause a single user to appear to be many, or many users to appear to be one. For any reliable and secure method of providing state, only information obtained from HTTP can be used.

The first step in maintaining state is to somehow uniquely identify each client. Because the only reliable information that can be used for such identification must come from the HTTP request, there needs to be something within the request that can be used for unique identification. There are a few ways to do this, but the solution designed to solve this particular problem is the cookie.

Cookies

The realization that there must be a method of uniquely identifying clients has resulted in cookies, a fairly creative solution. Cookies are easiest to understand if you consider them to be an extension of the HTTP protocol, which is precisely what they are. Cookies are defined by RFC 2965, although the original specification written by Netscape more closely resembles industry support.

There are two HTTP headers that are necessary to implement cookies, Set-Cookie and Cookie. A web server includes a Set-Cookie header in a response to request that the browser include this cookie in future requests. A compliant browser that has cookies enabled includes the Cookie header in all subsequent requests (that satisfy the conditions defined in the Set-Cookie header) until the cookie is expired. A typical scenario consists of two transactions (four HTTP messages):

  1. Client sends an HTTP request.
  2. Server sends an HTTP response that includes the Set-Cookie header.
  3. Client sends an HTTP request that includes the Cookie header.
  4. Server sends an HTTP response.

This exchange is illustrated in Figure 1.

Figure 1:

A Typical Cookie Exchange

The addition of the Cookie header in the client’s second request provides information that the server can use to uniquely identify the client. It is also at this point that the server (or a server-side PHP script) can determine whether the user has cookies enabled. Although the user can choose to disable cookies, it is fairly safe to assume that the user’s preference will not change while interacting with your application. This fact can prove to be very useful, as will soon be demonstrated.

GET and POST Data

There are two additional methods that a client can use to send data to a server, and these methods predate cookies. A client can include information in the URL being requested, whether in the query string or as part of the path. As an example of utilizing the query string, consider the following example request:

Toggle Code View

  1. GET /index.php?foo=bar HTTP/1.1
  2. Host: example.org

The receiving script, index.php, can reference $_GET['foo'] to reference the value bar. Because of this, most PHP developers refer to this data as GET data (others sometimes refer to it as query string data or URL variables). One common point of confusion is that GET data can exist in a POST request, because it is simply part of the URL being requested and doesn’t rely on the request method.

Another method that a client can use to send information is by utilizing the content portion of an HTTP request. This technique requires that the request method be POST, and an example of such a request is as follows:

Toggle Code View

  1. POST /index.php HTTP/1.1
  2. Host: example.org
  3. Content-Type: application/x-www-form-urlencoded
  4. Content-Length: 7
  5. foo=bar

In this case, the receiving script, index.php, can reference $_POST['foo'] to reference the value bar. PHP developers typically refer to this data as POST data, and this is how a browser passes data submitted from a form where the method is POST.

A request can potentially have both types of data, like this:

Toggle Code View

  1. POST /index.php?myget=foo HTTP/1.1
  2. Host: example.org
  3. Content-Type: application/x-www-form-urlencoded
  4. Content-Length: 11
  5. mypost=bar

These two additional methods of sending data in a request can provide substitutes for cookies. Unlike cookies, GET and POST data support is not optional, so these methods can also be more reliable. Consider a unique identifier called PHPSESSID included in the request URL as follows:

Toggle Code View

  1. GET /index.php?PHPSESSID=12345 HTTP/1.1
  2. Host: example.org

This achieves the same goal as the Cookie header, because the client identifies itself, but it is much less automatic for the developer. Once a cookie is set, it is the browser’s responsibility to return it in subsequent requests. To propagate the unique identifier through the URL, the developer must ensure that all links, form submission buttons, and the like contain the appropriate query string (PHP can help with this if you enable session.use_trans_sid). In addition, GET data is displayed in the URL and is much more exposed than a cookie. In fact, unsuspecting users might bookmark such a URL, send it to a friend, or do any number of things that can accidentally reveal the unique identifier.

Although POST data is less likely to be exposed, propagating the unique identifier as a POST variable requires all requests to be POST requests. This is not a convenient option, although your application design might make it more viable.

Session Management

Until now, I have been discussing state. This is a rather low-level detail that involves associating one HTTP transaction with another. The more useful feature that you are likely to be familiar with is session management. Session management not only relies on the ability to maintain state, but it also requires that you maintain data uniquely associated with each user. This data is often called session data, because it is associated with a specific user’s session. If you use PHP’s built-in session management mechanism, session data is persisted for you (in /tmp by default) and available in the $_SESSION superglobal. A simple example of using sessions involves the persistence of session data from one page to the next. Listing 1, start.php, demonstrates how this can be done.

Listing 1:

Toggle Code View

  1. <?php
  2. session_start();
  3. $_SESSION['foo'] = 'bar';
  4. ?>
  5. <a href="continue.php">continue.php</a>

Assuming the user clicks the link in start.php, the receiving script (continue.php) will be able to access the same session variable, $_SESSION['foo']. This is shown in Listing 2.

Listing 2:

Toggle Code View

  1. <?php
  2. session_start();
  3. echo $_SESSION['foo']; /* bar */
  4. ?>

Serious security risks exist when you write code like this without understanding what PHP is doing for you. Without this knowledge, you will find it difficult to debug session errors or provide any reasonable level of security.

Impersonation

It is a common misconception that PHP’s native session management mechanism provides safeguards against common session-based attacks. On the contrary, PHP simply provides a convenient mechanism. It is the developer’s responsibility to provide the appropriate safeguards for security. As mentioned previously, there is no perfect solution, nor a best solution that is right for everyone.

To explain the risk of impersonation, consider the following series of events:

  1. Good Guy visits http://example.org/ and logs in.
  2. example.org sets a cookie, PHPSESSID=12345.
  3. Bad Guy visits http://example.org/ and presents a cookie, PHPSESSID=12345.
  4. example.org mistakes Bad Guy for Good Guy.

These events are illustrated in Figure 2.

Figure 2:

An Impersonation Attack

Of course, this scenario assumes that Bad Guy somehow discovers or guesses the valid PHPSESSID that belongs to Good Guy. While this may seem unlikely, it is an example of security through obscurity and is not something that should be relied upon. Obscurity isn’t a bad thing, of course, and it can help, but there needs to be something more substantial in place that offers reliable protection against such an attack.

Preventing Impersonation

There are many techniques that can be used to complicate impersonation or other session-based attacks. The general approach is to make things as convenient as possible for your legitimate users and as complicated as possible for the attackers. This can be a very challenging balance to achieve, and the perfect balance largely depends on the application design. So, you are ultimately the best judge.

The simplest valid HTTP/1.1 request consists of a request line and the Host header:

Toggle Code View

  1. GET / HTTP/1.1
  2. Host: example.org

If the client is passing the session identifier as PHPSESSID, this can be passed in a Cookie header as follows:

Toggle Code View

  1. GET / HTTP/1.1
  2. Host: example.org
  3. Cookie: PHPSESSID=12345

Alternatively, the client can pass the session identifier in the request URL:

Toggle Code View

  1. GET /?PHPSESSID=12345 HTTP/1.1
  2. Host: example.org

The session identifier can also be included as POST data, but this typically involves a less friendly user experience and is the least popular approach.

Because information gathered from TCP/IP cannot be reliably used to help strengthen the security of the mechanism, it seems that there is little that a web developer can do to complicate impersonation. After all, an attacker must only provide the same unique identifier that a legitimate user would in order to impersonate that user and hijack the session. Thus, it would appear that the only protection is to either keep the session identifier hidden or to make it difficult to guess (preferably both).

PHP generates a random session identifier that is practically impossible to guess, so this concern is already mitigated. Preventing the attacker from discovering a valid session identifier is much more difficult, because much of this responsibility lies outside of the developer’s realm of control.

There are many situations that can result in the exposure of a user’s session identifier. GET data can be mistakenly cached, observed by an onlooker, bookmarked, or emailed. Cookies provide a safer mechanism, but users can disable support for cookies, ruling out the possibility of using them, and past vulnerabilities in Internet Explorer have been known to reveal cookies to unauthorized sites.

Thus, a developer can be fairly certain that a session identifier cannot be guessed, but the possibility that it can be revealed to an attacker is more likely, regardless of the method used to propagate it. Something additional is needed to help prevent impersonation.

In practice, a typical HTTP request includes many optional headers in addition to Host. For example, consider the following request:

Toggle Code View

  1. GET / HTTP/1.1
  2. Host: example.org
  3. Cookie: PHPSESSID=12345
  4. User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1
  5. Accept: text/html;q=0.9, */*;q=0.1
  6. Accept-Charset: ISO-8859-1, utf-8;q=0.66, *;q=0.66
  7. Accept-Language: en

This example includes four optional headers, User-Agent, Accept, Accept-Charset, and Accept-Language. Because these headers are optional, it is not very wise to rely on their presence. However, if a user’s browser does send these headers, is it safe to assume that they will be present in subsequent requests from the same browser? The answer is yes, with very few exceptions. Assuming that the previous example is a request sent from a current user with an active session, consider the following request sent shortly thereafter:

Toggle Code View

  1. GET / HTTP/1.1
  2. Host: example.org
  3. Cookie: PHPSESSID=12345
  4. User-Agent: Mozilla/5.0 (compatible; IE 6.0 Microsoft Windows XP)

Because the same unique identifier is being presented, the same PHP session will be accessed. If the browser is identifying itself differently than noted in previous interactions, should it be assumed that this is the same user?

It is hopefully clear that this is not desirable, yet this is exactly what happens if you do not write code that specifically checks for such situations. Even in cases where you cannot be sure that the request is an impersonation attack, simply prompting the user for a password can help prevent impersonation without adversely affecting your users too much. This is an important point.

You can add User-Agent checking to your security model with code similar to that Listing 3.

Listing 3:

Toggle Code View

  1. <?php
  2. session_start();
  3. if (md5($_SERVER['HTTP_USER_AGENT']) != $_SESSION['HTTP_USER_AGENT']) {
  4. /* Prompt for Password */
  5. exit;
  6. }
  7. /* Rest of Code */
  8. ?>

Of course, you will need to first store the MD5 digest of the user agent whenever you first begin a session, as shown in Listing 4.

Listing 4:

Toggle Code View

  1. <?php
  2. session_start();
  3. $_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
  4. ?>

While it is not necessary that you use the MD5 of User-Agent, it helps provide consistency and eliminates the necessity to filter $_SERVER['HTTP_USER_AGENT'] before using it. Because this data originates from a remote source, it should not be blindly trusted, but the format of an MD5 digest is consistent.

Now that you enforce User-Agent consistency, an attacker must complete two steps in order to hijack a session:

  1. Capture a valid session identifier.
  2. Present the victim’s User-Agent header in the impersonation attempt.

While this is clearly possible, it is more slightly more complicated, therefore the session mechanism is already more secure.

Other headers can be added in this way, and you can even use a combination of headers as a fingerprint. If you also include some secret padding of some sort, this fingerprint becomes practically impossible to guess. Consider the example Listing 5.

Listing 5:

Toggle Code View

  1. <?php
  2. session_start();
  3. $fingerprint = 'SHIFLETT' . $_SERVER['HTTP_USER_AGENT'];
  4. $_SESSION['fingerprint'] = md5($fingerprint . session_id());
  5. ?>

The Accept header should not be used in the fingerprint, because some browsers vary the value of this header when the user refreshes the page.

With a fingerprint that is difficult to guess, little is gained without using it. Consider a session mechanism where the fingerprint is propagated just like the session identifier. In this case, an attacker must complete the following three steps to successfully hijack the session:

  1. Capture a valid session identifier.
  2. Present the same HTTP headers used to generate the fingerprint.
  3. Present the victim’s fingerprint.

If both the session identifier and the fingerprint are propagated as GET data, it is possible that an attacker who can obtain one will also have access to the other. A safer approach is to utilize two different methods of propagation, GET data and cookies. Of course, this relies upon the user’s preferences, but an extra level of protection can be offered to those who enable cookies. Thus, if an attacker obtains the unique identifier by way of a browser vulnerability, the fingerprint is still likely to be unknown.

There are many more techniques that can be used to help strengthen the security of your session mechanism. Hopefully you are well on your way to creating some techniques of your own. After all, you are the expert of your own applications, so armed with a good understanding of sessions, you are the best person to implement some additional security.

Obscurity

I would like to dispel a common myth about obscurity. The myth is that there is “no security through obscurity.” As I mentioned earlier, obscurity is not something that offers adequate protection, nor should it be relied upon. However, this does not mean that there is absolutely nothing to be gained. Backed by a secure session mechanism, obscurity can offer a bit of additional security.

Simply using misleading variable names for the session identifier and fingerprint can help. You can also propagate decoy data to mislead a potential attacker. These techniques certainly should never be relied upon for protection, of course, but you will not waste your time by implementing a bit of obscurity in your own mechanism.

Summary

I hope that you have gained several things from this article. Notably, you should now have a basic understanding of how the web works, how statefulness is achieved, what a cookie really is, how PHP sessions work, and some techniques that you can use to improve the security of your sessions.

If you enjoyed this article, you might also be interested in these others:

If you develop a secure session mechanism of your own, please feel free to share it with the community. I would love to hear about your own solutions, and I hope this article provides the background information necessary to support your own creativity.

2 Comments more...

IIS Compression – enable

by admin on Dec.21, 2008, under IIS, asp

@echo off
rem http://www.pipeboost.com
net stop iisadmin

REM Setup IIsCompressionScheme for deflate
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/DEFLATE/HcCompressionDll %windir%\system32\inetsrv\gzip.dll
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/DEFLATE/HcCreateFlags 0
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/DEFLATE/HcDoDynamicCompression TRUE
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/DEFLATE/HcDoOnDemandCompression TRUE
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/DEFLATE/HcDoStaticCompression TRUE
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/DEFLATE/HcDynamicCompressionLevel 9
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/DEFLATE/HcFileExtensions “htm” “html” “txt” “js” “xml” “css”
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/DEFLATE/HcOnDemandCompLevel 9
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/DEFLATE/HcPriority 1
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/DEFLATE/HcScriptFileExtensions “asp” “dll” “exe” “aspx” “asbx” “ashx” “axd”

REM Setup IIsCompressionScheme for GZip
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/GZIP/HcCompressionDll %windir%\system32\inetsrv\gzip.dll
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/GZIP/HcCreateFlags 1
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/GZIP/HcDoDynamicCompression TRUE
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/GZIP/HcDoOnDemandCompression TRUE
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/GZIP/HcDoStaticCompression TRUE
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/GZIP/HcDynamicCompressionLevel 9
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/GZIP/HcFileExtensions “htm” “html” “txt” “js” “xml” “css”
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/GZIP/HcOnDemandCompLevel 9
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/GZIP/HcPriority 1
cscript c:\inetpub\adminscripts\adsutil.vbs set W3Svc/Filters/Compression/GZIP/HcScriptFileExtensions “asp” “dll” “exe” “aspx” “asbx” “ashx” “axd”

REM Setup IIsCompressionSchemes parameters
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcCacheControlHeader max-age=86400
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcCompressionBufferSize 102400
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcCompressionDirectory %windir%\”IIS Temporary Compressed Files”
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcDoDiskSpaceLimiting FALSE
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcDoDynamicCompression TRUE
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcDoOnDemandCompression TRUE
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcDoStaticCompression TRUE
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcExpiresHeader “Wed, 01 Jan 1997 12:00:00 GMT”
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcFilesDeletedPerDiskFree 256
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcIoBufferSize 102400
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcMaxDiskSpaceUsage 0
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcMaxQueueLength 1000
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcMinFileSizeForComp 1
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcNoCompressionForHttp10 FALSE
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcNoCompressionForProxies FALSE
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcNoCompressionForRange FALSE
cscript c:\inetpub\adminscripts\adsutil.vbs set W3SVC/Filters/Compression/Parameters/HcSendCacheHeaders TRUE

net start w3svc

pause

3 Comments more...

table to html

by admin on Mar.18, 2008, under IIS, asp

Public Function TableToHtml(ByVal t As System.Web.UI.WebControls.Table) As String

Dim Builder As New StringBuilderDim Writer As New System.IO.StringWriter(Builder)

Dim HTMLWriter As New Html32TextWriter(Writer, ” “)t.RenderControl(HTMLWriter)

Return Builder.ToStringEnd Function

Leave a Comment more...

IIS Compression- disable selected

by admin on Mar.17, 2008, under IIS, asp

@echo off
net stop iisadmin

REM Setup WebService IIsCompressionScheme
cscript C:\Inetpub\AdminScripts\adsutil.vbs set w3Svc/829054140/root/DoDynamicCompression TRUE
cscript C:\Inetpub\AdminScripts\adsutil.vbs set w3Svc/829054140/root/DoStaticCompression TRUE

net start w3svc

pause

Leave a Comment more...

Mastering The Back Button With Javascript

by admin on May.26, 2007, under IIS, asp

Ask how to control the back button on a forum and you’ll be quickly lectured how
interfering with the browser’s history is evil and that you’re a bad person for
wanting to do it. You’ll then be told it’s impossible. But, guess what? Both
groups are wrong!

Almost everyone who asks how to control the back button is trying to make pages
accessed while a user was logged in, inaccessible once a user has logged out.
A good example of this is a banking page. If you log in, transfer a few funds,
then log out, no one should be able to hit the back button a few time and see
what you were doing.

So wanting to control the back button isn’t an evil request. There are many,
MANY legitimate uses for wanting to do so.

window.onbeforeunload

Around Internet Explorer version 4.0, Microsoft decided there needed to be
a way for secure banking and e-commerce sites to have some control over the
session history. They did this with the javascript event onbeforeunload,
a very funky little method that is called right before the browser navigates
to a new URL.

There are two things that happen when you set an onbeforeunload event. One, you
can give the user a chance to re-think the decision to leave the page by spawning
a “yes/no” dialog box; and two, just having an onbeforeunload event will cause the
browser to never cache the page: This means that the page will always be
rendered exactly as if the user was hitting it for the very first time.

onbeforeunload is a method of window (the global/root javascript object).
You set up the event pretty much like any other event.

window.onbeforeunload = function () {
   // stuff do do before the window is unloaded here.
}

Are you really, really sure you want to leave my glorious page?

What makes onbeforeunload quirky is that if you return ANYTHING at
all in your event handler it will pop up a confirmation alert box asking the
user if he or she is REALLY sure they want to leave the page. The exact text is:

Are you sure you want to navigate away from this page?

Click OK to continue, or Cancel to stay on the current page.

This dialog box will appear if you return ANYTHING in your function. return null,
return false, return true, return “some text” will all bring up the confirmation
dialog box.

You can insert your own explanatory text BETWEEN those two lines by including
a string on the return statement. For instance:

window.onbeforeunload = function () {
   return "You have not saved your document yet.  If you continue, your work will not be saved."
}

…will change the alert box to read…

Are you sure you want to navigate away from this page?

You have not saved your document yet.  If you continue, your work will not be saved.

Click OK to continue, or Cancel to stay on the current page.

As you can see, the starting and ending lines stay the same, your text
is inserted into the middle.

If the user clicks ok, the onunload event will trigger and the browser will
navigate to the new page. If the user clicks cancel the user will be returned
to the current page where he or she left off.

You can see an example of this by clicking this link. If you click OK
this timestamp should change (Timestamp: 1180247244687). If you click cancel, the timestamp should remain the same.

If you don’t want a confirmation dialog box, simply do not include a return statement
in your function.
Just close the curly braces ( } ) naturally without
a return statement.

window.onbeforeunload = function () {
   // This fucntion does nothing.  It won't spawn a confirmation dialog
   // But it will ensure that the page is not cached by the browser.
}

Detecting When The User Has Clicked Cancel

One of the things you may want to do is to be notified when the user clicks
cancel, aborting a page unload. Unfortunately there’s no way to be immediately
notified. The best you can do is to set a unique global variable in your onbeforeunload
event and then look to see if that variable has been set in other functions. There is
no way to get an immediate notification that the user has aborted a page unload.

The example code I used above to do an example of an onbeforeunload dialog
is as follows:

var _isset=0;

function demo() {
   window.onbeforeunload = function () {
      if (_isset==0) {
         _isset=1;  // This will only be seen elsewhere if the user cancels.
         return "This is a demonstration, you won't leave the page whichever option you select.";
      }
   }
   _isset=0;
   window.location.reload();
   return false;
}

This code defines a global variabled named _isset, and then initializes it to zero.
In our onbeforeunload event the variable is checked and if it’s set to one,
no unload dialog box will appear. The only way _isset could ever be one is if
the user previously aborted a page unload.

But as you can see this method won’t help you if you need to be immediately notified
that that the user has finished dealing with the confirmation box. You can detect when
it appears on the screen but there’s no way to know when the user has finished interacting
with it if the user clicked cancel (if the user clicked OK, then of course the unload
event will have been tripped).

Truly Dynamic Pages

Just having an unbeforeunload event handler — regardless of whether or not
it actually does anything, regardless of whether or not you spawn a dialog
box or not, even if the entire function declaration consists entirely of just { }
– just defining an event handler will prevent the page from being cached –
ever.

As a matter of fact, even if you allow page caching, the page will be not be
cached. Having an onbeforeunload event means the page will be re-built
every single time it is accessed.
Javascripts will re-run, server-side scripts
will be re-run, the page will be built as if the user was hitting it for the
very first time, even if the user got to the page just by hitting the back or forward button.

Some security considerations.

Although onbeforeunload ensures the page will be fresh each time,
you may still want to issue meta commands and server-side headers to control
the cache — just to be sure the browser doesn’t store a copy on the sly. You
can do this with server side headers (php examples here)…

header("Cache-Control: no-cache, must-revalidate");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Pragma: no-cache");

And with browser meta tags (not as reliable but it never hurts to try).

<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
<META HTTP-EQUIV="EXPIRES" CONTENT="01 Jan 1970 00:00:00 GMT">
<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">

Now in the what were they thinking department, you want to put cache
control meta tags in a <head> section below the
<body> tag. Microsoft has a good writeup
on why this is so. Just mark this up as yet another browser idiosyncrasy to contend with.

Good cache-control will reduce the chance that someone can go through the physical
cache on the user’s machine and extract sensitive information.

Logged out? No page for you!

Since onbeforeunload event pages are not cached and are rebuilt each
time the user access it, it means your server side script can determine if the
user is logged in or not and serve the appropriate pages. You can even see if
the session has timed out and redirect the user to the login page.

Shutdown Nicely

In addition to disabling page caching, onbeforeunload gives you a
chance to nicely handle unload events. For instance you could submit the current
form data, marked as incomplete and rebuild it if the user comes back to the page.
Another good example of this is handling session
logging. By incorporating a small synchronous AJAX call you can log when the user left
the page, giving you an idea of how long each user is staying on the page.
If you don’t set up a confirmation dialog, this activity would be completely
transparent to the user. Here’s a simple template you can use
to do just that.

window.onbeforeunload = function() {
   // This template uses no error checking, it's just concept code to be
   // expanded on.

   // Create a new XMLHttpRequest object
   var AJAX=new XMLHttpRequest();  

   // Handle ready state changes ( ignore them until readyState = 4 )
   AJAX.onreadystatechange= function() { if (AJAX.readyState!=4) return false; }

   // we're passing false so this is a syncronous request.
   // The script will stall until the document has been loaded.
   // the open statement depends on a global variable titled _userID.
   AJAX.open("GET", 'http://someurl.com/endsession.php?id='+_userID, false);
   AJAX.send(null);
}

The choice to use a synchronous call is deliberate and important. A synchronous
request will stall the browser until it gets a reply back from the server. If we
were to initiate an asynchronous request the request would be made but the browser
would continue the unload event and chances are the browser would never get the
response back from the server. So in your unload events you need to keep AJAX
synchronous.

Browser Check

onbeforeunload was introduced with Internet Explorer 4, so it has pretty
good support. A lot of users surf without javascript however so if you decide
to use onbeforeunload to help manage your session you’ll need to make
extensive use of <noscript></noscript> and dynamic HTML generation through
javascript to nudge users into enabling javascript, at least for your pages. On
the server side you’ll also need to do some browser checking to ensure the browser
supports onbeforeunload.

You can’t change history

The history object, for good reason, has been put into a security lockdown.
You can’t use it to modify the history. You can however have some small and
very imperfect control over the current url.

location.replace(url)

Using the command above you can replace the current page with the url of your
choice, this replaces the current URL in the user’s history as well. If
the page is VERY sensitive you can use this when the user opts to log out.
Firefox will let you use location.replace(URL) right before the return statement
in an onbeforeunload event. That is, if you do a location.replace and
then spawn a confirmation box, the current page will be altered.

window.onbeforeunload = function () {
   location.replace('http://www.google.com');
   return "This session is expired and the history altered.";
}

It’s kind of clunky in that if the user opts to cancel he’ll be on the replacement
page instead of the original page, and the replacement page will actually be loaded
as he ponders the confirmation dialog. If he selects OK however he’ll navigate
forward normally and if he hits the back button he’ll be taken to the replacement
URL.

It’s important to note than in Internet Explorer 6 that you can not use
a location method at all in any unload event, so the trick of modifying the history of the current page
will work (kinda) only in Firefox. It’s not something you can depend on.

If you MUST control History

If you absolutely must be able to clear out the history, then spawn a new
window (without a location line) and when the user is done, close the window.
The URLs will still be in the browser’s history list ( cntrl-h ), but the
session’s backward and forward button list will have been destroyed.

Conclusion

Really, manipulation of the browser’s history is unnecessary as long as you
are dynamically generating your web pages and you use onbeforeunload events
to ensure the page will always be fresh. If the page is being dynamically generated
then even if someone goes through the history list and clicks on one of your
session URLs, it won’t really matter because the page will be rebuilt from scratch
meaning you can check to see if the session has expired and if the user is logged in
or not. If the check is failed, you simply re-direct the browser to a login page.

And there you go! Everything you need to know to ensure secure sessions, stay
secure and private, no matter who sits down at the browser an hour later.

4 Comments more...

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!

Visit our friends!

A few highly recommended friends...