Django, and the story of a CRSF cookie

Gregory Terzian
3 min readJul 26, 2017

--

Django ships with crsf protection. The other day however, I was left scratching my head over why the crsf cookie wasn’t present in my browser. At that time, the prototype was deployed on a .heroku.com domain for testing.

At first, I cycled througt a few possible source for the problem:

  1. The Host header, even though I set ALLOWED_HOSTS to [*]?
  2. Heroku.com being part of the Public Suffix List?
  3. The fact that CSRF_COOKIE_SECURE was set to True, even though I also set SECURE_PROXY_SSL_HEADER to (‘HTTP_X_FORWARDED_PROTO’, ‘https’), as Heroku suggests.
  4. Any of the following settings CSRF_TRUSTED_ORIGINS, CSRF_COOKIE_DOMAIN, and other CRSF settings, which I frankly never messed with before.

Alas, nothing seemed to make that crsf cookie appear in my browser…

In the end, I concluded the problem related to setting cookies in general, including the ones used for login, so I just quickly implemented a authentication hack using a hardcoded token and disabled crsf protection on every view with the crsf_exempt decorator.

I assumed the cookie problem would go away once I would have re-deployed the app away from Heroku onto our own infrastructure.

Couple of weeks later — now on our own infrastructure — still no crsf cookie present…

After a read through Django’s CsrfViewMiddleware, and with the help of a few good old fashioned print statements, I was able to ascertain that inside process_response, the condition that request.META.CSRF_COOKIE_USED was False was hit, and that the response was returned without a crsf cookie added to it.

So I started thinking, when is this request.META.CSRF_COOKIE_USED set to True?

It turns out it is set to True by a “get_token” function, which is part of Django’s internals. When is that function called? For example, when you use the “csrf_token” in your template.

Eureka! This made me realize that, since the front-end was all done in React, and I wasn’t using any forms in a django template, my one and only ‘home’ template wasn’t using the “csrf_token” tag, hence it was never set on the response and not available for the Javascript to find either…

Now it turns out, the good folks contributing to Django are aware of this. They’ve documented it, and even made available a decorator — aptly named “ensure_crsf_cookie” — that indeed sets the crsf cookie.

Unfortunately, the answer pointing to this decorator, in response to the problem “Django CSRF Cookie Not Set” — itself the top result to the google search “django not setting crsf cookies” — has only received 2 upvotes.

The answer pointing to CSRF_COOKIE_SECUR— which, while correct, is exactly the kind of wild-goose chase I also initially got myself into — has received 61 upvotes.

Anyone smelling a hint of our love of complexity, while ignoring the simple and most obvious solution that is staring right at us?

Errata:

A previous version of this article referred to the “requires_csrf_token”. That’s because I made a mistake reading both the django docs and the stackoverflow answer(which refers to the correct decorator).

The decorator you are looking for isn’t “requires_csrf_token”, it’s “ensure_crsf_cookie”.

It took me one test cycle to figure it out. Actually I almost just deployed the app without testing because I was so overconfident it would work…

I must be having bad day, or the django community have invented a few decorators too many?

Since crsf cookies are routinely reset on every login, do we really need this complicated logic? Should the default not simply be “set a crsf cookie on every response that doesn’t already include one?”? Especially in this day and age when most people probably don’t use django templates that much anymore, and even less people actually render a form with a “crsf_token”?

Parting thoughts…

In general, I see this as a lesson — our understanding of a given problem is, at any time, very limited. This was actually a very simple problem, and I got it very wrong for quite some time.

Tools that allow us to manage our limited understanding of reality — like automated testing, building with “conventions over configurations” in mind, and simply sticking to the principle of least surprise — are not some ‘nice to have’ optional. They're the difference between failure and success of any (software) project.

--

--

Gregory Terzian

I write in .js, .py, .rs, .tla, and English. Always for people to read