10

I have a Django view with a form that uses CSRF protection. I want this view to be cached by Varnish when there is a normal GET request (since all users need the same form, no login).

So there are two challenges:

  1. How to cache this page in Varnish and not delivered cached/old versions of the csrf hidden field to the user? Is it at all possible to cache pages with CSRF field?

  2. My Varnish by default strips out all cookies, how could I easily make it strip all cookies, except the csrftoken cookie? And do I have to set a specific CSRF_COOKIE_DOMAIN?

Anders E
  • 211
  • 3
  • 6

3 Answers3

9

This is a couple of years late, but here's how I got around this problem recently.

The trick is to use ESI, which varnish supports. We take the CSRF snippet and stick it into its own page, including it via ESI when going through varnish, and directly otherwise (such as when running the local dev server).

csrf_esi.html:

{% csrf_token %}

csrf_token.html

{% if request.META.HTTP_X_VARNISH_USE_CACHE %}
<esi:include src="{% url 'esi_csrf_token' %}" />
{% else %}
{% include "csrf_esi.html" %}
{% endif %}

urls.py

from django.conf.urls import url
from django.views.generic import TemplateView

urlpatterns = [
    ...
    url(r'csrf_esi.html', TemplateView.as_view(template_name="csrf_esi.html"), name='esi_csrf_token'),
]

csrf_esi.py

from django import template

register = template.Library()

@register.inclusion_tag('csrf_token.html', takes_context=True)
def csrf_token_esi(context):
    return context

settings.py

TEMPLATES = [
    {
        ...
        'OPTIONS': {
            ...
            'builtins': [
                'path.to.csrf_esi',
            ],
        }
    }
]

Varnish config

set req.http.X-Varnish-Use-Cache = true;

You also need to whitelist the csrf_esi.html page so it never gets cached and add set beresp.do_esi = true; inside the vcl_fetch function. I'd elaborate more on this, but I didn't set this part of the system up and am not 100% clear myself.


Now you can simply use it like you do the normal {% csrf_token %} tag:

<form action="">
    {% csrf_token_esi %}
    <button type="submit">Push me</button>
</form>

It's quite a bit to set up, but once you do, you'll never have to look at it again.

Anonymous
  • 11,740
  • 3
  • 40
  • 50
  • 1
    And what about the CSRF cookie? Django checks both the CSRF hidden form field and the CSRF cookie. This solution only solves the problem with the hidden field, isn't it? – srus Aug 04 '16 at 15:09
  • @srus +1, what about CSRF Cookie? that is the problem, not the hidden field input. Varnish by default does not cache the input hidden token! – Jota Jul 08 '21 at 09:57
8

Using CSRF on a view essentially means that each render of the view is inherently different (even though only the value of one hidden field is changing). Caching doesn't work in such a scenario.

However, Django does provide mechanisms for getting around this limitation, namely cookies, as you seem to already have guessed. So on your second part, there's two things that need to be done:

  1. Set up Django to send CSRF cookies instead of using the hidden field. (See: https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#s-caching)
  2. Make Varnish disregard the cookie Django sends. (See: http://www.varnish-cache.org/docs/trunk/tutorial/cookies.html)

You only need to set CSRF_COOKIE_DOMAIN in Django if the request will be coming from a different domain than where it is processed.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • 1
    I'd like a little clarification what you mean in 1) "Set up django to send CSRF cookies instead of using the hidden field" -- is there a way to do this? It was my understanding you needed to include the {% csrf_token %} tag in all forms (which creates the hidden field). – coleifer Jul 30 '11 at 15:13
  • 3
    I don't think this solution is viable. Django CSRF protection works by setting a cookie and checking that its value matches that of a hidden field. In other words, it needs both a hidden field and cookies to vary for each request. – Eli Sep 01 '12 at 22:00
  • @Eli: but Varnish only needs a cookie to vary on. – Chris Pratt Sep 04 '12 at 14:17
1

I had similar issues using @csrf_protect and AJX, if anyone is using this decorator this might help

As well as adding exceptions to Varnish. Make sure that both the view with the form and the view that the data is being posted to use the decorator.

I only had @csrf_protect on the post view which worked fine testing locally but when i went live o got a 403 verification failed issue adding the decorator tot he page view fixed this

sidarcy
  • 2,958
  • 2
  • 36
  • 37