Hi, my site has a searchbox on each page, so I placed a {% csrf_token %}
tag in the base template. Activating a per-site cache (or a per-view cache with the @cache_page
and @csrf_protect
decorators) does not affect the site’s performance at all, as the pages change with each request, so nothing is ever cached. Am I right, or is there some magical way around this (other than template fragment caching)?
I believe you are correct.
That doesn’t mean there aren’t possible work-arounds. I can think of two right off-hand.
- URL-encode the contents of the searchbox, and submit it as a GET instead of a POST
- If the view invoked by the searchbox is truly safe, mark that view as csrf_exempt
Thank you! I am not confident that my search view (s. below) is truly safe, and am sufficiently terrified by CSRF prevention recommendations like this to avoid workarounds. I have tested template fragment caching successfully, although this approach does not leverage browser caching.
def search(request):
if request.method == 'POST':
form = SearchForm(request.POST)
if form.is_valid():
results = Page.objects.filter(title__icontains=form.cleaned_data['page_title'], content__icontains=form.cleaned_data['page_content'], author__last_name__icontains=form.cleaned_data['page_author']).order_by('title')
return render(request, 'results.html', { 'results' : results,} )
else:
form = SearchForm()
return render(request, 'form.html',
{
'target' : reverse(search),
'form' : form,
})
PS. You are one of the most friendly and competent people I met in any technical forum. Thanks for your time and patience.
A search would be “safe” in this context if there is no data being written. (No objects being created or updated, and no “user status” change such as a login/logout.)
I would have the opinion that this view would be considered safe.
Hi, I faced the same issue. According to the CSRF docs, you can use:
from django.views.decorators.cache import cache_page
from django.views.decorators.csrf import csrf_protect
@cache_page(60 * 15)
@csrf_protect
def my_view(request): ...
But this is kind of useless, since this just adds the Vary: Cookie
header to the response, witch means the cache stores a separate version for each user, where the only real difference is the CSRF token—whether in a cookie or the masked token from {% csrf_token %}
in the template…
Another way around is to modify a bit how the cache_page decorator works by calling the get_token() method after retrieving the cached response. This of course only works to set the Set-Cookie http header on the response but it won’t work for the {% csrf_token %} on a template. In witch case you are forced to use AJAX, as mentioned in the docs.
from django.middleware.cache import CacheMiddleware
from django.middleware.csrf import get_token
from django.utils.decorators import decorator_from_middleware_with_args
class CSRFCacheMiddleware(CacheMiddleware):
def process_request(self, request):
response = super().process_request(request)
get_token(request)
return response
def secure_cache_page(timeout, *, cache=None, key_prefix=None):
return decorator_from_middleware_with_args(CSRFCacheMiddleware)(
page_timeout=timeout,
cache_alias=cache,
key_prefix=key_prefix,
)