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.