Browser developers long ago recognized that HTTP’s statelessness poses a huge problem for Web developers, and thus cookies were born. A cookie is a small piece of information that browsers store on behalf of Web servers. Every time a browser requests a page from a certain server, it gives back the cookie that it initially received. Let’s take a look how this might work. When you open your browser and type in google.com, your browser sends an HTTP request to Google that starts something like this:
GET / HTTP/1.1
Host: google.com
…
When Google replies, the HTTP response looks something like the following:
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671;
expires=Sun, 17-Jan-2038 19:14:07 GMT;
path=/; domain=.google.com
Server: GWS/2.1
…
Notice the Set-Cookie header. Your browser will store that cookie value (PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671) and serve it back to Google every time you access the site. So the next time you access Google, your browser is going to send a request like this:
GET / HTTP/1.1
Host: google.com
Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671
…
Google then can use that Cookie value to know that you’re the same person who accessed the site earlier. This value might, for example, be a key into a database that stores user information. Google could (and does) use it to display your name on the page.
Getting and Setting Cookies – When dealing with persistence in Django, most of the time you’ll want to use the higher-level session and/or user frameworks discussed a little later in this chapter. However, we’ll pause and look at how to read and write cookies at a low level. This should help you understand how the rest of the tools discussed in the chapter actually work, and it will come in handy if you ever need to play with cookies directly. Reading cookies that are already set is incredibly simple. Every request object has a COOKIES object that acts like a dictionary; you can use it to read any cookies that the browser has sent to the view:
def show_color(request):
if “favorite_color” in request.COOKIES:
return HttpResponse(“Your favorite color is %s” % \
request.COOKIES[“favorite_color”])
else:
return HttpResponse(“You don’t have a favorite color.”)
Writing cookies is slightly more complicated. You need to use the set_cookie() method on an HttpResponse object. Here’s an example that sets the favorite_color cookie based on a GET parameter:
def set_color(request):
if “favorite_color” in request.GET:
# Create an HttpResponse object…
response = HttpResponse(“Your favorite color is now %s” % \
request.GET[“favorite_color”])
# … and set a cookie on the response
response.set_cookie(“favorite_color”,
request.GET[“favorite_color”])
return response
else:
return HttpResponse(“You didn’t give a favorite color.”)
You can also pass a number of optional arguments to response.set_cookie() that control aspects of the cookie, as shown in Table 12-1.
Parameter | Default | Description |
max_age | None | Age (in seconds) that the cookie should last. If this parameter is None, the cookie will last only until the browser is closed. |
expires | None | The actual date/time when the cookie should expire. It needs to be in the format “Wdy, DD-Mth-YY HH:MM:SS GMT”. If given, this parameter overrides the max_age parameter. |
path | “/” | The path prefix that this cookie is valid for. Browsers will only pass the cookie back to pages below this path prefix, so you can use this to prevent cookies from being sent to other sections of your site.
This is especially useful when you don’t control the top level of your site’s domain. |
domain | None | The domain that this cookie is valid for. You can use this parameter to set a cross-domain cookie. For example, domain=”.example.com” will set a cookie that is readable by the domains www.example.com, www2.example.com, and an.other.sub.domain.example.com.
If this parameter is set to None, a cookie will only be readable by the domain that set it. |
secure | False | If set to True, this parameter instructs the browser to only return this cookie to pages accessed over HTTPS. |
The Mixed Blessing of Cookies – You might notice a number of potential problems with the way cookies work. Let’s look at some of the more important ones:
- Storage of cookies is essentially voluntary; browsers don’t guarantee anything. In fact, all browsers enable users to control the policy for accepting cookies. If you want to see just how vital cookies are to the Web, try turning on your browser’s “prompt to accept every cookie” option.
Despite their nearly universal use, cookies are still the definition of unreliability. This means that developers should check that a user actually accepts cookies before relying on them.
More important, you should never store important data in cookies. The Web is filled with horror stories of developers who’ve stored unrecoverable information in browser cookies only to have that data purged by the browser for one reason or another. - Cookies (especially those not sent over HTTPS) are not secure. Because HTTP data is sent in cleartext, cookies are extremely vulnerable to snooping attacks. That is, an attacker snooping on the wire can intercept a cookie and read it. This means you should never store sensitive information in a cookie.
There’s an even more insidious attack, known as a man-in-the-middle attack, wherein an attacker intercepts a cookie and uses it to pose as another user. - Cookies aren’t even secure from their intended recipients. Most browsers provide easy ways to edit the content of individual cookies, and resourceful users can always use tools like mechanize (http://wwwsearch.sourceforge.net/mechanize/) to construct HTTP requests by hand.
So you can’t store data in cookies that might be sensitive to tampering. The canonical mistake in this scenario is storing something like IsLoggedIn=1 in a cookie when a user logs in. You’d be amazed at the number of sites that make mistakes of this nature; it takes only a second to fool these sites’ “security” systems.