Set cookies the right way
This is part of a series of posts I’m doing as a sort of Python/Django Advent calendar, offering a small tip or piece of information each day from the first Sunday of Advent through Christmas Eve. See the first post for an introduction.
Cookies in the cookie jar
Django’s request and response objects, and their attributes and methods, make dealing with cookies easy. You can read from the request.COOKIES
dictionary to get a cookie, and you can use the response’s set_cookie()
method to set cookies. What else do you need?
Well, potentially a few things.
First of all, it’s worth reviewing those arguments to set_cookie()
, because several of them are important. You should probably always set:
secure=True
: This instructs browsers to only send the cookie on secure (i.e., HTTPS) requests, never on unencrypted connections. This helps avoid some potential leaks of cookie values — for example, if the user bookmarked or manually typed or pasted in a plain HTTP URL, there’s a chance of visiting it first before being redirected to an HTTPS version, and that initial request will not use an encrypted connection, which exposes the values of all cookies in the request to anyone who can observe the network traffic. For a similar reason, you should set the Django settingsSESSION_COOKIE_SECURE
andCSRF_COOKIE_SECURE
both toTrue
to ensure those cookies are only sent on HTTPS connections.samesite="Lax"
(if not"Strict"
): This controls whether a cookie is sent when making cross-site requests. Setting to at least"Lax"
will shut down quite a lot of CSRF attack vectors. TheSESSION_COOKIE_SAMESITE
andCSRF_COOKIE_SAMESITE
Django settings control this for the session and CSRF cookies, and default to"Lax"
.
You should also strongly consider setting httponly=True
when possible; this will instruct the browser not to let JavaScript access that cookie (though it will still be sent normally with requests). The Django settings SESSION_COOKIE_HTTPONLY
and CSRF_COOKIE_HTTPONLY
control this for the session and CSRF cookies; the session cookie defaults to True
, while the CSRF cookie defaults to False
(since, as the documentation points out, if an attacker can already run JavaScript that would access that cookie, you’re busted anyway).
Django also supports using signed cookies as a tamper-proofing mechanism. To set a signed cookie, use the set_signed_cookie()
method, and to read it, use the get_signed_cookie()
method (instead of directly accessing request.COOKIES
). You should also look into providing the salt
argument, which acts as an additional input to the signing algorithm to help partition different usages of Django’s signing utilities (which by default use the value of your SECRET_KEY
setting as the signing key; specifying the salt will combine the salt value and the SECRET_KEY
to produce a unique signing key. This prevents use of signing tools in one part of a site from acting as an oracle for signing on another part of the same site.
Just be careful about trying to do too much with signed cookies. Signing only gives you a check against tampering, and doesn’t encrypt or otherwise usefully obscure the value stored in the cookie, and browsers tend to have a limit (usually 4KB) on how much data they can store in a single cookie, so trying to put large amounts of data in a signed cookie isn’t a great idea.