We all know and love cookies, for the liberties they allow us, and their delicious taste. If you've ever tried to share your cookies, however, you know that it's not as easy as it sounds.
Okay, cutting the baker talk. Cookies are very useful for developers - keeping track of users, performing many functions cheaper than more expensive databases, personalization and customization, etc. Cookies are not transferrable across domains; the only domain that can access the cookie is the domain that created it. This article takes a look at how to bypass this limitation using Active Server Pages.
Cookies - A Brief Introduction
We're going to take a short detour and explain cookies a bit, and show you briefly how to manipulate them using ASP.
A cookie is a small file that is stored on a client's computer. That means that whenever a user visits your web site, you can secretly stash a file with information on their hard drive. This file can contain almost any information you want - including user info, site statistics, or even your own name, for those with vanity problems. We can see how this could potentially be an easy target for hackers because of the possibilities it opens up.
One security trick to prevent abuse is that cookies can only be accessed by the domain that created them. This means that, for example, ASP101.com can only access (read from and write to) cookies that ASP101.com created. Generally, this is not a problem at all, but what if you work on two different sites on different domains that share user info, that is stored in cookies? You could, of course, just duplicate user info, but what if you want the user to only have to register on one site, and be automatically registered on the other? Or if they share the same user database, and you want to auto-login users? Sharing cookies across domains is ideal for this situation.
Before we get into that however, let's briefly show some ASP code that can manipulate cookies, so we have something to refer to later in the article.
'To write a cookie
Response.Cookies("MyCookie").Expires = Date + 365
Response.Cookies("MyCookie").Domain = "mydomain.com"
Response.Cookies("MyCookie")("Username") = strUsername
Response.Cookies("MyCookie")("Password") = strPassword
Reading and writing cookies is very simple. The above code sets a few properties for the cookie, the expiration date and the domain, and also sets a few values to be stored in the cookie. In this case, strUsername, and strPassword are variables we assigned somewhere earlier. Then, to read from a cookie, you simply request the values:
For more detailed code using ASP, check out this sample on ASP 101.
The Easy Way
The secret to sharing cookies easily is redirection. Here is the general procedure:
A user hits siteA.com
If user does not have cookie for siteA.com then redirect user to siteB.com
If user has a cookie for siteB.com then redirect back to siteA.com with a special identifier (explained below), else, just send user back to siteA.com
Write cookie on siteA.com
Sounds pretty simple, huh? Let's expound a bit: siteA.com and siteB.com share the same set of users, and therefore if someone has a cookie (and is registered) at siteB.com, then siteA.com wants to be able to view those cookies as well, and provide whatever features the cookies allow. This way, visitors to siteA.com will have a similar experience to those at siteB.com.
The key to this checking should be done in a cookies.inc file that you include on your pages on siteA.com. Let's take a look at the code on siteA.com:
'Check for a cookie
If Request.Querystring("Checked") <> "True" then
If not Request.Cookies("SiteA_Cookie").Haskeys then
'redirect to siteB.com
If the user already has a cookie for siteA.com, then we don't need to do anything - simply process the rest of the page. The first if statement is used to eliminate infinite loops, and doesn't really make sense by itself. Let's look at cookie.asp on siteB.com to gain a better understanding:
'Check for a cookie
If not Request.Cookies("SiteB_Cookie").Haskeys then
'redirect to siteA.com
strUsername = Request.Cookies("SiteB_Cookie")("Username")
'send back to siteA.com with unique identifier
& "?checked=True&identifier= & strUsername)
If the user also doesn't have a cookie on siteB.com, then send them back to siteA.com and let the application know that you've already checked for the cookie by supplying a variable called "checked" in the querystring. Otherwise, it will send the user right back to siteB.com, and you'll end up with an infinite loop.
Note: You can be sneaky here and redirect the user back to a registration page, or something similar. If you have an aggressive customization scheme going, you can do interesting things by displaying different message (for instance, "special deals" for an e-commerce site) based on the cookie information.
However, if the user does have a cookie on siteB.com, we need to send the user back and let siteA.com know about it. Hence, we attach a variable that uniquely identifies this user in our database, their username. Now, let's expand upon the code on siteA.com. The following code goes below what is already there in table 1.1:
'Check for an identifier
If Request.Querystring("identifier") <> "" then
strUsername = Request.Querystring("identifier")
'perform some database stuff here (optional)
Response.Cookies("SiteA_Cookie").Expires = Date + 365
Response.Cookies("SiteA_Cookie").Domain = "siteA.com"
Response.Cookies("SiteA_Cookie")("Username") = strUsername
Finally, we're back on siteA.com. The first part of this file (as written in table 1.1) would check to see whether the cookie check has been accomplished, and, since it obviously has (as indicated by the "checked" parameter in the querystring), we move onto the second part of the code displayed in table 1.3. If there is a specified identifier, then we can now build the cookie on siteA.com. Using the identifier (in this case, the user name), we could query a database or perform whatever manipulation we may need, if any at all. Then we set the cookie, and display the rest of the page. If there is no identifier specified, then we don't worry about any of this, and simply display the rest of the page as you would normally.
Voila! SiteA.com now has the same cookie as siteB.com, and you didn't even have to break a sweat. You can transfer more data than simply a unique identifier, as we did, but here we wanted to keep the network traffic, as well as exposed data, to a minimum.
One thing to be aware of is that even if a user has a cookie on siteA.com, you may still want to check siteB.com. Generally, this isn't necessary, and will save you and the user a little time, what if perhaps the user changed his/her personal info on siteB.com? This would be an easy way to make sure that all information is in sync.
To perform this little trick, we needed two files: one on the originating server (siteA.com), to perform the check; and one on the reference server (siteB.com), to validate the user. Note that you can potentially add as many originating servers as you want - if you have one reference server that contains all the cookies or user info you need, then all you need to do is add the cookies.inc file to all new servers that wish to share cookies.
This can also work in the opposite direction, for instance, if siteB.com were the originating server, and siteA.com had the user info. Users new to siteB.com but familiar to siteA.com could log onto siteB.com and have all their familiar settings already pre-set for them. Note, however, that if you have more than one reference server, this can get quite confusing and resource demanding - you would have to redirect the user to each reference site to see if they exist.
Theoretically, you could have an entire network of sites that all share the same users. The most practical solution in this situation would be to create a ring of cookie sharing, or a cookie ring (you got to love these food analogies). Store a list of the reference servers in one place (the backbone server) that each reference server could look at to determine the next site to send the user to. Note that you would have to keep track of the original site the user started from, possibly by means of a querystring variable. In a world that's moving rapidly toward super-connectivity, this scenario becomes more and more feasible.
There are a few issues with this method, the foremost being the response times. The user will most likely never know what's going on, since the application is fairly transparent, but the load times they experience may be long depending on the connection between siteA.com and siteB.com, and even longer if you implement a cookie ring. Generally, it isn't a problem, but you can never predict network traffic.
Another major problem, that almost everyone who tries to implement this will probably face, is infinite redirects. This can happen for a number of reasons - a typo in the cookie scripts, if the user's browser doesn't support cookies, etc. Most likely it is a very simple problem to fix such as I've described, but don't be surprised if you see the status bar at the bottom of your browser go crazy with redirects when you try it out. Make sure you test this out on all available platforms and browsers before you implement this on a real site.
Finally, we can't forget about security. If some clever hacker found out about your little trick, it may be possible for them to sneak in and steal some cookies. A simple way to protect against this is to guard your reference server - only allow your originating server to access the cookies.asp file, or double check that the IP or referring address is acceptable.
There is, of course, another way to perform this feat without resorting to redirection tricks. Unfortunately, though, it is more difficult. I won't go into depth in this article, but I will outline a few features.
The idea behind this second method is a component (a COM object) that resides on the reference server. When a user hits siteA.com, you invoke a call to that component, or an ASP page on the reference sever, that invokes that component. This component then returns all the cookie information on siteB.com for use on siteA.com. Note that this architecture is highly supported by the new Simple Object Access Protocol (SOAP), which allows applications to be used across the internet with simple XML. You could also implement the code on the originating server in a COM object as well.
This method offers several advantages over the previous redirection methods. First, there are no redirects involved, so you won't have to worry about infinite loops or excessive load times. There will be additional network overhead as you invoke the component, but no more than the previous method. Also, this method is much more secure and scalable - it allows for more servers to be added to the cookie ring easier and more reliably.
Sharing cookies is a very nice method to share user data and build a network of user friendly sites. Be careful, however, of user's privacy, as this is a great concern nowadays. Imagine, after all, if Amazon.com and Ebay.com teamed up to share user info in this way - one on hand, the companies would be able to offer the consumers more and better choices, but at what expense of the user's privacy? That, however, is another article.
Check out these links for further resources on manipulating cookies and topic covered in this article: