Django: Fix CSRF Token Cookie not generating for non-html pages

This was a bit of a tricky one to debug. I was creating an API to log into the site via an app which communicated through JSON.

If the user was already logged in, the JSON response contained:

  • the account details
  • session ID (automatically set)
  • the CSRF token from request.META["CSRF_COOKIE"]

Otherwise, the user had to use HTTP POST to gain access to functionality. Since there wasn't any CSRF information yet, I decided to unprotect that view using @csrf_exempt.

This worked fine, as the JSON response was still giving back the right information:

  • account details
  • session ID
  • CSRF token from request.META["CSRF_COOKIE"]

The strange thing was that the CSRF token was being regenerated on each request, making any AJAX type calls fail the CSRF test every time even with "X-CSRFToken" set.

After a little investigation by delving into the Django source files, I realised there were 2 reasons why this was happening.

Firstly, the view to check/login was using @csrf_exempt. The cookie won't be set if the view is exempt. Removing the exemption case is fine, as the app will always perform an initial GET fetch request to check existing login data.

Strangely, this was still not enough to generate the "csrftoken" cookie.

A little more investigation by browsing made me realise something, it's generating when I view regular pages but not my special JSON view.

The view isn't terribly complicated either. Can you spot the problem causing the CSRF token to not be set?

c = {
'success': u.is_authenticated(),
'username': u.username if u.is_authenticated() else '',
'csrf_token': request.META["CSRF_COOKIE"],
}

return HttpResponse(JSONEncoder().encode(c))

No? Don't worry, I didn't either.

The cookie won't be set unless the content/mime type of the page is a known HTML type (such as "text/html" or "application/xhtml+xml").

So apart from changing the mime-type, what can we do?

Easy. Just add in this line just before returning HttpResponse.

# This triggers CsrfViewMiddleware to make it set the CSRF token upon first request.
request.META["CSRF_COOKIE_USED"] = True

That's all it needed. That sneaky little bastard wasn't in the docs when I looked at it, but it should solve this fairly niche issue.

81366568
Now, off to solve more important problems!

 
Copyright © Twig's Tech Tips
Theme by BloggerThemes & TopWPThemes Sponsored by iBlogtoBlog