Recently I started a new project and decided to build on django,
and use piston for handling crud operations. I also decided
to utilize django’s user authentication system and the
django.contrib.auth app, which leads to problems with piston and it’s
handling, or lack there of, of CSRF tokens.
Why use django’s CSRF protection?
Simply, the django.contrib.auth app requires CSRF protection.
Protection can be accomplished a couple ways, but I decided to use
CsrfViewMiddleware and CsrfResponseMiddlware to provide blanket
protection across the application, which protects forms in other
applications as well. (And, I believe this will be the default once
django 1.2 is released.)
Adding the CSRF middleware means every form using the POST method
now has a special token, named csrfmiddlewaretoken, inserted as a hidden
field before it is sent to the web client. The CsrfResponseMiddleware
does this. Then, for every POST request from the web client, the
CsrfViewMiddleware checks the token to ensure it contains the expected
value, if not, a 403 http status is returned.
Full details of django’s CSRF middleware and CSRF tokens can be found in the django developer docs.
So, what’s the real problem?
All our POST requests and responses are protected from CSRF attacks. But
piston is an api framework and it’s arguable that CSRF doesn’t make a lot
of sense in an api, because the api clients must know the CSRF token prior
to making the request. As such there is no CSRF handling in piston and the
following error is thrown by BaseHandler.create()
FieldError: Cannot resolve keyword 'csrfmiddlewaretoken' into field.when piston tries to retrieve a model instance from the database and
encounters the csrfmiddlewaretoken field.
However, I don’t believe there is any reason a web client cannot and should not be considered a valid api client, and I believe piston should handle the token one way or another. So, how did I work around this constraint and error thrown by piston?
Solution 1
First, I thought about making all requests handled by piston exempt from CSRF handling. This requires 2 things:
1. decorate your view with csrf_exempt to prevent the csrfmiddlewaretoken field from being
added to the request, eg:
from django.contrib.csrf.middleware import csrf_exempt from django.shortcuts import render_to_response from wherever.the.model.form.is import ModelForm @csrf_exempt def form_view(request): form = ModelForm() return render_to_response('form.html', {'form': form})
2. modify your piston resource to make it csrf_exempt (i did this in
urls.py):
from django.conf.urls.defaults import patterns from piston.resource import Resource from wherever.the.model.handler.is import ModelHandler class CsrfExemptResource(Resource): """A Custom Resource that is csrf exempt""" def __init__(self, handler, authentication=None): super(CsrfExemptResource, self).__init__(handler, authentication) self.csrf_exempt = getattr(self.handler, 'csrf_exempt', True) resource = CsrfExemptResource(ModelHandler) urlpatterns = patterns('', (r'^the_path$', resource), )
Thanks to Brian Zambrano in the piston issue 82 forum for this idea, the only difference between my implementation and his, he modified the piston Resource class code, and I extended it so that I didn’t have to modify the piston source.
Solution 2
Solution 1 works great, but if django goes to all the trouble to implement CSRF tokens by default, it would be nice to use them and use them with piston.
To maintain protection and resolve the problem I wrote my own handler base
class which extends pistons’s BaseHandler and removes the
csrfmiddlewaretoken prior to calling create() and subsequent calls that
retrieve the model instance from the database.
class CsrfExemptBaseHandler(BaseHandler): """ handles request that have had csrfmiddlewaretoken inserted automatically by django's CsrfViewMiddleware """ def flatten_dict(self, dct): if 'csrfmiddlewaretoken' in dct: # dct is a QueryDict and immutable dct = dct.copy() del dct['csrfmiddlewaretoken'] return super(CsrfExemptBaseHandler, self).flatten_dict(dct)
Then, to use, you would extend CsrfExemptBaseHandler instead of piston’s
BaseHandler in your handler code, eg:
class YourHandler(CsrfExemptBaseHandler): """Your Handler functionality""" model = models.YourModel # define read, create, update, delete as needed
With that, your handler’s create method should now work and POST requests
are protected and handled without error.
I am using Solution 2 in my application because I wanted to maintain protection.
Finally, as I mentioned above I do believe web clients are valid api clients and it would be nice to have CSRF protection one way way or another in piston, especially since it looks like CSRF middleware will be on by default in django 1.2. But, if not, it is relatively easy to roll your own solution using one of the above.
How would you use CSRF in the API? CSRF works by delivering a token to the client upon serving the form, and when using an API, you don't have this luxury.
thanks for this! I was getting 403's with django 1.2.3 and piston and this was exactly my problem.
I wonder CsrfBaseHandler is CsrfExemptBaseHandler in "return super(CsrfBaseHandler, self).flatten_dict(dct)" code?
hello, I have setup a piston interface to perfom all the CRUD operations. I don't want to use the csrf middleware at all. Also my django server does not use forms etc, its a simple server hosting some REST apis.
I have disable the csrf view in the settings.py but the POST method (create) does not get executed and i get a error 400-Bad request. I see the same behavior when i use the methon 2 mentioned above. please help!
We are a group of volunteers and opening a new scheme in our community. Your website provided us with valuable information to work on. You have done a formidable job and our entire community will be thankful to you.
Solution 1 worked great! Thank you. I tried solution 2, but it didn't work. Perhaps I missed something.
Solution 2 worked great for me, thanks a lot.
@Chirag: the 400 Bad Request could because you have malformed POST data, or you're posting data in a format that your Piston API is not expecting.
Ref: Solution 1
Instead of extending the resource class, this also seems to work for me:
File: urls.py
from django.contrib.csrf.middleware import csrf_exempt
from piston.resource import Resource
from app.handlers import AppHandler
app_handler = csrf_exempt(Resource(AppHandler))
urlpatterns = patterns('main.views',
)
Any reason we shouldn't do this?
Fair enough.
For this, I'd suggest you forget all about CSRF, and rely on something like OAuth, which authenticates and verifies a lot more, by signing the request, including the URL, body, etc.
The actual CSRF should happen between the application serving and receiving the form from the client, it shouldn't be necessary between the server and the API.
The reason why it's a "problem" in Piston, is that I'm using Django's built in Form generation classes. The csrf_excempt flag should be on by default, and most likely will be in future releases.