.Net Dojo: Cookies with ASP.NET 2.0
Recently I was testing the cookie class for my library of utilities called Olympus. This library consist of commonly used things like cookies, a database provider, memory caching, error reporting, and other tools. I stumbled across a big change in the way cookies are handled in .NET 1.1 and .NET 2.0. Here’s how I made cookies work.
In 1.1 I was able to append a cookie into the response and then pull it back out of the request and extract any data, including the expiration date. If the cookie did not exist, null was returned, which made it easy to determine of the cookie did not exist. The code worked great, and I ported it to 2.0 line for line. Then, while testing I discovered that the cookies were not returning the expiration data anymore. After much exploration and testing, I found that in 2.0 the expiration date is not returned by the client for security reasons. Also, in 2.0 a cookie is always returned, instead of returning null. This changed my entire model. When I built my cookie class, called BaseCookie I decided to build an object that modeled System.Web.HttpCookie and work in the actions I would use to add, update, and retrieve values in the cookie. The base class would not incorporate any encryption. I used a class called EncryptedCookie that inherited from BaseCookie to override methods and incorporate encryption. Because I wanted to be able to turn the encryption on and off, I built a custom configuration that would let me alter cookie settings and enable or disable the encryption. EnsureCookie() is called in the EncryptedCookie’s constructor. This method makes sure the cookie exists and if not it appends the cookie into the response.
protected void EnsureCookie()
{
//if the cookie doesn't exist in the response (hasn't been altered or set yet this postback)
if (IsNull(ResponseCookie))
{
//if the cookie exists in the request
if (!IsNull(RequestCookie))
{
//get the cookie from the request
HttpCookie cookie = RequestCookie;
//update the expiration
if (ExpirationMode == ExpirationMode.NoExpiration)
{
cookie.Expires = DateTime.Now.AddYears(10);
}
else
{
cookie.Expires = DateTime.Now.AddMinutes(Timeout);
}
//set the cookie into the response
HttpContext.Current.Response.Cookies.Set(RequestCookie);
}
else
{
//if the response and request cookies are null, append a new cookie
AppendNewCookie();
}
}
}
IsNull is a custom method that uses several pieces of information to make sure the cookie is null. Since the expiration date always equals DateTime.Min, I also have to test the value of the cookie to determine if it is “null”. To get around this, Every time the cookie is created, I pass in a value. In most casts it is an identifier, such as the user Id, the e-mail address, or login name.
protected static bool IsNull(HttpCookie cookie)
{
if (cookie == null)
{
return true;
}
else
{
if (String.IsNullOrEmpty(cookie.Value) && cookie.Expires == DateTime.MinValue)
{
return true;
}
}
return false;
}
If the cookie doesn’t exist in either the response or the request, a new one must be made. AppendCookie takes care of this.
public virtual void AppendNewCookie()
{
HttpCookie cookie = new HttpCookie(CookieName);
cookie.Domain = Domain;
cookie.HttpOnly = HttpOnly;
cookie.Path = Path;
cookie.Secure = Secure;
cookie.Value = Value
if (ExpirationMode == ExpirationMode.NoExpiration)
{
cookie.Expires = DateTime.Now.AddYears(10);
}
else
{
cookie.Expires = DateTime.Now.AddMinutes(Timeout);
}
HttpContext.Current.Response.Cookies.Set(cookie);
}
In BaseCookie I have methods for getting and setting values in the cookie. In EncryptedCookie they are overidden to pass in encrypted data.
public virtual void SetKeyValue(string key, string value)
{
ResponseCookie.Values.Set(key, value);
SetExpiration();
}
public virtual string GetKeyValue(string key)
{
return ResponseCookie.Values[key];
}
Every cookie has a default item that has no key. For this I created these methods. Note: the use of SetCookieValue will override all values in the cookie, but the get only returns the default value.
public virtual void SetCookieValue(string value)
{
ResponseCookie.Value = value;
SetExpiration();
}
public virtual string GetCookieValue()
{
string returnValue = "";
if (ResponseCookie.Values.Count > 0)
{
returnValue = ResponseCookie.Values[0];
}
return returnValue;
}
Expiration of the cookie is as simple as it was previously. I just subtract one day from the current datetime to set it as yesterday. It took getting all the other pieces right before I managed to get this to work.
public virtual void Expire()
{
ResponseCookie.Expires = DateTime.Now.AddDays(-1);
}
SetExpiration() is called whenever the cookie data is accessed. It resets the Expiration to slide as it is used. This is important or the cookie will be sent back to the client with an Expiration Date of DateTime.Min. In the cookie is persistant (ExpirationMode.NoExpiration), I add 10 years just to ensure the cookie isn’t going to expire.
protected void SetExpiration()
{
if (ExpirationMode == ExpirationMode.NoExpiration)
{
ResponseCookie.Expires = DateTime.Now.AddYears(10);
}
else
{
ResponseCookie.Expires = DateTime.Now.AddMinutes(Timeout);
}
}
After about 6 hours of trial and error, I finally figured out how cookies work in 2.0. It’s a big change from 1.1 and if you aren’t aware it can be a big surprise when your cookie always exists, even with no data and never seems to expire.
Addendum: November 19, 2007
I have uploaded a couple of the code files I am using in my Olympus.Cookies namespace. Olympus.Encryption is not included for security reasons, so this code will not build as is until you create your own encryption method unless you do not include EncryptedCookie. You can download the files from my Box.net account. The files encluded are the BaseCookie class, which is the base class for my cookie model. Then, there is EncryptedCookie, which wraps the BaseCookie class with encryption that can be overridden in the web.config. Finally, there is the cookie class, which I use to add any extra cookies to my projects. All cookies are defined via the Web.Config with a special configuration class. For my security model, I have another class called Olympus.Authentication which houses all the security logic and uses 2 cookies - one for the data and one to store a redirect url for use in redirecting the user after login. Using the files I have provided you should be able to get a better idea of how to implement cookies in 2.0 in a reusable library with whatever security model is best for your business.
1 Comment »
Leave a comment
-
Archives
- June 2008 (3)
- May 2008 (1)
- March 2008 (1)
- February 2008 (6)
- January 2008 (6)
- November 2007 (18)
-
Categories
-
RSS
Entries RSS
Comments RSS
Hi. I’m getting an error defining ExpirationMode.
Please tell me how should implement it?
Would you please post the code for that enum?
Thanks in advance.