Django-rest-framework: SessionAuthentication is always forcing csrf check even on csrf_exempt views

Created on 21 Mar 2017  路  9Comments  路  Source: encode/django-rest-framework

Current implementation of SessionAuthentication assums that CSRF context-less check (without view handler context) is always fired - regardles the fact that csrf_exempt was used to decorate view.

It is not possible currently to define a view that will be exempted from CSRF check for unsafe operation while using Session auth backend.

I found this particulary annoying when implementing login view for API used in CORS, as my Javascript frontend did not have access to cookies that was set on different domain (so session cookie was always sent) but at the same time has lost his csrf token stored localy - so it cannot log again because django will always throw

HTTP 401 - CSRF token missing or incorrect.

The only workaround is to manually provide somehow a new token via e.g. GET method from django to Frontend.

PS. Also I cannot find any rationale for the way CSRF force checking on Session is implemented right now - that is discarding and overriding the intentional use of csrf_exempt.

Most helpful comment

It is not possible currently to define a view that will be exempted from CSRF check for unsafe operation while using Session auth backend.

If you want to do that use a customized session authentication class, CSRFExemptSessionAuthentication. If someone really wants they could package that up and we could link to it in the docs as a third party package.

All 9 comments

I'm missing a point here. How can you use a login page that's protected by SessionAuthentication ?

@xordoquy It's not protected (session authentication is working regardles view is protected or not). Django automaticly assignes a user to the request when sessionid cookie is present. And this cookie might be just present without awerness of Frontend JavaScript - as we are talking about CORS usage - JavaScript cannot access cookies for backend api domain.

Does this make sens to you now?

PS. Also I cannot find any rationale for the way CSRF force checking on Session is implemented right now - that is discarding and overriding the intentional use of csrf_exempt.

That's because Django's CSRF is all or nothing. It forces DRF to decorate the views with csrf_exempt so that unsafe actions made with non session auth can still be made without CSRF.
DRF then enforces the CSRF check only for the SessionAuthentication.

The view to authenticate on an API should not have authentication no matter whether or not there's a permission check. And once you are logged, the browser will have the CSRF cookie for the other unsafe requests.
If you don't want to log the user a second time, there are a lot of solutions to support single authentications across domains. JWT or OAuth are two common examples.

@xordoquy so you are saying that by default(without writing custom auth backend), when I use DRF, I'm forbidden to exempt some views(explicitly!) from being CSRF-checked? I know reasoning of having CSRF protection enabled by default and I agree with it but can't understand why I can't turn it off when I know what I'm doing? https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/authentication.py#L133 - this is what we're talking about

@mrbox I said that you are already csrf exempted unless the request is unsafe and the request is authenticated by session auth. It's clearly stated in the documentation.

I'd be interested in an example where you need to be logged and also need the csrf_exempt.

@xordoquy thank you for clarification, however I cannot find your "all or nothing" approach satisfing nor legitimate. It is because architecture decision made on Sessions Backend brake internal django assumptions, as request that have attribute request.csrf_exempt = True are counterintuitively checked in some magic second pass of csrf machinery (conditionaly!) rather than have this state changed at certain stage of request processing.
I would prefer solution that use django-middleware to detect user session authentication and change the state of request.csrf_exempt to False for further processing of CSRFMiddleware - that would be much more straightforward. In such scenario there could exists a settings flag variable deciding on enabling/disabling csrf force mode, that could be disabled to manage endpoints manualy with full responsibility.

As an example of case where I would use a csrf exempted endpoint while being logged is when I do not control cookies (CORS scenario) and flushed localstorage of frontend app will result in sending no-csrf token while sending still valid session_id (automatically by browser). In this case Javascript frontend app is completly blocked from possibility to being re-authorized (and receive new csrf token). Even - if for login endpoint there is zero sense for asking/checking any csfr token, nor there should be any assumption that allready logged user cannot re-authenticate again (possibly to different user).

Also please note that JWT are terrible choice for implementing sessions. This is a great antipattern mostly used because of misunderstanding what are JWT and what purpose does it solve (further reading: http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/)

@cypreess thanks for the link about JWT. Will take some time to get the argument. Seems there are a couple of very interesting points.

If you have a better option to make the csrf_exempt decorator work with DRF, feel free to open an issue and expose your solution. I'd be really interested in hearing about. I wanted to do something like that and ended with the conclusion that current design is the best option but I probably missed some points so feel free.

Even - if for login endpoint there is zero sense for asking/checking any csfr token, nor there should be any assumption that allready logged user cannot re-authenticate again (possibly to different user).

I'd also be interested in a test case to showcase that CSRF issue. According to me, the login view should not have any authentication and thus will not be subject to CSRF with DRF regardless of whether or not you are already logged.

@xordoquy OK - I will try to prepare some failing test first.

It is not possible currently to define a view that will be exempted from CSRF check for unsafe operation while using Session auth backend.

If you want to do that use a customized session authentication class, CSRFExemptSessionAuthentication. If someone really wants they could package that up and we could link to it in the docs as a third party package.

Was this page helpful?
0 / 5 - 0 ratings