Setting, Deleting, and Accessing Cookies in Django (+ some common gotchas!)

Overview I’m mot going to do a deep dive into cookies and their use in the web on the whole. If you think an introduction like that would help you understand this post better, here is one place you might start.

The very high-level that will apply to what we’re going to talk about here is this:

  • Cookies are stored by your browser
  • They’re basically key-value pairs, with some attributes
  • Your browser sends relevant cookies along with requests to your server.
  • When you send a response back to the browser, you can tell it to set new cookies, or update existing cookies
  • With that out of the way, let’s get to how to do all of this in Django!

Each request object made available in a Django view has a COOKIES attribute, which returns a dictionary containing key value pairs, both of which are strings.

So you could access the value of a specific cookie by running:

cookie_val = request.COOKIES[cookie_name]

Unfortunately, browsers don’t send additional information about the cookies, such as when it expires or which domain it was set on. If you need access to this information within your application logic, you will need to include it in the value of the cookie itself.

To tell a browser to set a cookie, you call ‘set_cookie’ on the HTTP response object before returning it. The key is required and the value defaults to an empty string (sometimes the presence or absence of a cookie as all you need to know, making the value irrelevant). All other keyword arguments are optional, and are explained in the Django documentation if you’re unsure how to use them!

Calling set_cookie with the key of a cookie that already exists will overwrite the existing cookie, effectively replacing it.

Beware! Because the COOKIES attribute of a request is a mutable dictionary, it can be tempting to add or edit values in it. And while something like request.COOKIES[‘new_key’] = ‘new_value’ won’t throw an error…it also won’t have any impact. Browsers set cookies based on HTTP responses they receive, so cookies need to be set on the response object for the browser to process them.

Just like setting a cookie, Django has a function called delete_cookie that can be used for this purpose, and takes a subset of the arguments passed into set_cookie.

This seems straightforward, but there are a few things that can trip you up, and cause cookies to not actually be deleted. According to the docs:

Due to the way cookies work, path and domain should be the same values you used in set_cookie() – otherwise the cookie may not be deleted.

This is interesting! Let’s dig into this, just a little. If you look at how Django has implemented delete_cookie, you’ll see this;

def delete_cookie(self, key, path='/', domain=None, samesite=None):
    self.set_cookie(key, max_age=0, path=path, domain=domain, samesite=samesite, expires='Thu, 01-Jan-1970 00:00:00 GMT')

This de-mystifies the need to use the same attributes that were used to set the cookie, to delete the cookie. Browsers don’t have the concept of “deleting” a cookie. There’s only telling the browser “this cookie is expired now.” Which means that if you don’t use the same attributes that were used to set the cookie originally, instead of expiring your existing cookie, it will create a new one that we’ll never see because it’s immediately expired.

The addition of widespread browser support (and enforcement, in the case of Chrome) for SameSite cookies is a fairly recent addition. (If you’re unsure about how or when to use the SameSite attribute on cookies, I found this to be a very comprehensive and approachable overview). Django versions 2.1 and newer provide a samesite keyword argument for setting and deleting cookies. However, even if you’re running on an older version of Django than that, you may still need to apply this logic to be in compliance with more recent browser updates.

It’s a little less clean than using the Django-provided keyword argument in newer versions, but the good news is, your response object has a cookies attribute that you can use to set additional attributes on your cookie. Like so:

response.cookies[cookie_name]['SameSite'] = <“none”/”lax”/”strict”>

This gets a little more complicated when we talk about deleting cookies. Similar to the requirement that cookies be deleted with the same path and domain attributes with which they were created, you may not be able to delete a cookie without the correct SameSite value. As we’ve established, if you’re running an older version of Django, you don’t have access to this via the built in delete_cookie function. To get around this, you’ll need to implement your own deletion, by running set_cookie with a max_age of 0, and then manually setting the SameSite attribute, as we did above.