Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Django's CSRF-protection on views cached by Varnish

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?

like image 879
Anders E Avatar asked Jun 09 '11 11:06

Anders E


2 Answers

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.

like image 102
Anonymous Avatar answered Sep 21 '22 12:09

Anonymous


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.

like image 24
Chris Pratt Avatar answered Sep 17 '22 12:09

Chris Pratt