A word about CSRF Protection and AJAX

142 views
Skip to first unread message

Sayane

unread,
Feb 19, 2011, 6:00:31 AM2/19/11
to django-d...@googlegroups.com
There's a problem with CSRF Protection and XHR requests. It works perfectly if 'csrftoken' cookie has been set already. But what if it's not?
Cookie with token will be set only, if META["CSRF_COOKIE_USED"] is True [1]. It's set to True in function get_token() [2]. get_token() is called in CsrfResponseMiddleware [3] (It's deprecated, i'm not using it) and in 'csrf' context processor (note - calling it is lazy, so I need to use {% csrf_token %} or at least get the value of csrf_token variable).

But in my project i'm not using {% csrf_token %} anywhere. According to documentation [5] I'm not required to do anything else, but write a simple javascript code. Actually it's not true. I have to put "request.META['CSRF_COOKIE_USED'] = True" line in every view (or write appropriate decorator).

What is more, it will affect users who didn't come across page where csrf_token is used, but their browser needs to send xhr post request.

It affects svn version. I don't know if other versions are affected.

Luke Plant

unread,
Feb 19, 2011, 11:57:57 AM2/19/11
to django-d...@googlegroups.com
On Sat, 2011-02-19 at 12:00 +0100, Sayane wrote:
> There's a problem with CSRF Protection and XHR requests. It works
> perfectly if 'csrftoken' cookie has been set already. But what if it's
> not?
> Cookie with token will be set only, if META["CSRF_COOKIE_USED"] is
> True [1]. It's set to True in function get_token() [2]. get_token() is
> called in CsrfResponseMiddleware [3] (It's deprecated, i'm not using
> it) and in 'csrf' context processor (note - calling it is lazy, so I
> need to use {% csrf_token %} or at least get the value of csrf_token
> variable).
>
> But in my project i'm not using {% csrf_token %} anywhere. According
> to documentation [5] I'm not required to do anything else, but write a
> simple javascript code. Actually it's not true. I have to put
> "request.META['CSRF_COOKIE_USED'] = True" line in every view (or write
> appropriate decorator).

> What is more, it will affect users who didn't come across page where
> csrf_token is used, but their browser needs to send xhr post request.

I guess this is an edge case - it would typically be very rare for
someone to be doing POST AJAX requests who has never hit a page with {%
csrf_token %} on it - even if it was just the login page.

The docs for AJAX are meant to be in *addition* to the docs on the rest
of the page, which state, among other things, that you need to include a
{% csrf_token %} on the page. This bit (step 2 in "How to use it") could
be clarified for the case of AJAX-only sites.

So, my suggested solution is some small doc fixes, and the addition of a
decorator 'ensure_csrf_cookie' that ensures the cookie will be sent. The
correct way to implement this decorator is to simply add a call to
django.middleware.csrf.get_token(). META["CSRF_COOKIE_USED"] is an
implementation detail that we are free to change, so don't rely on
setting that explicitly. If you would like to open a ticket to track
this, that would be great.

Regards,

Luke


--
"Agony: Not all pain is gain." (despair.com)

Luke Plant || http://lukeplant.me.uk/

Sayane

unread,
Feb 19, 2011, 12:21:30 PM2/19/11
to django-d...@googlegroups.com
http://code.djangoproject.com/ticket/15354

2011/2/19 Luke Plant <L.Pla...@cantab.net>

--
You received this message because you are subscribed to the Google Groups "Django developers" group.
To post to this group, send email to django-d...@googlegroups.com.
To unsubscribe from this group, send email to django-develop...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.


Jonas Obrist

unread,
Feb 23, 2011, 8:07:44 AM2/23/11
to django-d...@googlegroups.com
I beg to differ luke.

Most of our AJAX POSTs we do are actually not a 'form'. Because we usually submit forms with 'normal' POST requests. 

What would be so terrible in just setting the cookie always?

Jonas

Luke Plant

unread,
Feb 23, 2011, 6:07:14 PM2/23/11
to django-d...@googlegroups.com
On Wed, 2011-02-23 at 05:07 -0800, Jonas Obrist wrote:
> I beg to differ luke.
>
>
> Most of our AJAX POSTs we do are actually not a 'form'. Because we
> usually submit forms with 'normal' POST requests.

I was suggesting that normally you would encounter at least one normal
form before doing AJAX, in which the cookie would be set. And if you
apply the principles of graceful degradation, then you will have a
normal HTML form which includes the token, as well as having the AJAX
stuff. Obviously that depends on the app, and it is becoming less and
less true, with more and more apps that depend entirely on javascript.

> What would be so terrible in just setting the cookie always?

Hmm, good question. I guess just the fact of sending stuff that you may
not need seems like bad practice.

If we changed it to always send the cookie, we would need to ensure that
we only send the 'Vary: Cookie' header if the token was actually used in
the page. Implementing this is actually a fairly trivial patch - just
re-ordering a few lines.

If there was objection to always sending the cookie, we might need a
setting to control that. But I'm loathe to do that and add further
complications to the docs.

Luke

--
As Ralph Waldo Emerson once said, "I hate quotations."

Luke Plant || http://lukeplant.me.uk/

Jonas Obrist

unread,
Feb 23, 2011, 6:23:45 PM2/23/11
to django-d...@googlegroups.com
Well writing a middleware in my app or decorating all views seems a little hacky/unclean to me too.

In our specific use case, the django CMS the graceful degrading is done through the admin, our so called frontend editing is heavily javascript and AJAX base, without HTML forms. therefore we have a lot of problems now Django (correctly) checks for the CSRF header in AJAX request. to make this backwards incompatibility easier for developers to adopt, always sending the cookie would be the right thing to do, in my opinion.

Jonas

Luke Plant

unread,
Feb 25, 2011, 10:49:27 AM2/25/11
to django-d...@googlegroups.com

Sorry, I forgot to continue this conversation.

I'm quite happy to entertain the idea that the CSRF middleware should
always set the CSRF cookie, but would like to know what other devs
think.

The main consequence I can think of is this:

If a page has 'Vary: Cookie' sent in the headers, and was not previously
sending cookies, the new cookie being sent will cause different cache
behaviour i.e. it won't be cached internally in Django or in other
caches.

This is a significant enough performance consideration to make me
hesitate, but Jonas' arguments about ease of use for people using AJAX
are also significant.

If we change it, do we want to change it before the 1.3 release? And
backport it to 1.2.X?

Luke

--
"Because Your lovingkindness is better than life,
My lips shall praise You." (Ps 63:3)

Luke Plant || http://lukeplant.me.uk/

Ryan N

unread,
Feb 25, 2011, 12:01:36 PM2/25/11
to Django developers
On Feb 25, 9:49 am, Luke Plant <L.Plant...@cantab.net> wrote:
> Sorry, I forgot to continue this conversation.
>
> I'm quite happy to entertain the idea that the CSRF middleware should
> always set the CSRF cookie, but would like to know what other devs
> think.
>
> The main consequence I can think of is this:
>
> If a page has 'Vary: Cookie' sent in the headers, and was not previously
> sending cookies, the new cookie being sent will cause different cache
> behaviour i.e. it won't be cached internally in Django or in other
> caches.
>
> This is a significant enough performance consideration to make me
> hesitate, but Jonas' arguments about ease of use for people using AJAX
> are also significant.
>
> If we change it, do we want to change it before the 1.3 release? And
> backport it to 1.2.X?
>
> Luke

When we upgraded 4 of our Django sites to 1.2.5, we ended up having to
write/include a trivial middleware that went something like this:

from django.middleware.csrf import get_token
class CsrfCookieUsedMiddleware(object):
"""
Simple middleware that ensures Django's CSRF middleware will
always include the CSRF cookie on outgoing responses.

"""
def process_request(self, request):
get_token(request)

We needed that because we CSRF protect our logout link on all
authenticated pages (using jQuery to submit a POST when you click the
link, if no JS then you get taken to a logout page with a logout
form). We also have feedback links on lots of pages that we don't
render forms for if you have javascript enabled, instead it opens in a
lightbox and submits an AJAX post.

The problem with that simple middleware or changing Django to always
include the cookie is we do NOT want that behavior on any view
decorated as CSRF exempt. For example, we have a site that has both
the site code and its RESTful APIs running out of the same Django
project, so there we had to modify that 'always include the CSRF
cookie' middleware because we didn't want that cookie coming back in
any API responses, just the website responses.

Just figured I'd throw our use cases out there...

Paul McMillan

unread,
Feb 26, 2011, 5:05:29 AM2/26/11
to django-d...@googlegroups.com
I'll chime in as opposed to forcing the cookie where not strictly
necessary. It makes caching harder, and means that many of the dumber
caching systems out there won't cache it at all (some mobile
operators, smaller ISPs in lower GDP countries, etc.). Pushing the
cookie to make AJAX easier seems especially silly if the site doesn't
use AJAX at all.

-Paul

Reply all
Reply to author
Forward
0 new messages