Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Test CSRF Verification with Django Rest Framework

I'm using Django Rest Framework 3 and would like to test the CSRF verification.

First, I initialize the DRF APIClient:

client = APIClient(enforce_csrf_checks=True)

Then I set a password on a user so I can login and get a session:

superuser.set_password('1234')
superuser.save()
client.login(email=superuser.email, password='1234')

Now we need a CSRF token. For that I simply create a request and retrieve the token from the cookies.

response = client.request()
csrftoken = client.cookies['csrftoken'].value

When inspecting the code, this seems to work, I get back a valid looking CSRF token. I then do the POST request, passing in the csrfmiddlewartoken parameter:

data = {'name': 'My fancy test report', 'csrfmiddlewaretoken': csrftoken}
response = client.post(API_BASE + '/reports', data=data, format='json')
assert response.status_code == status.HTTP_201_CREATED, response.content

The problem is, this fails:

tests/api/test_api.py:156: in test_csrf_success
    assert response.status_code == status.HTTP_201_CREATED, response.content
E   AssertionError: {"detail":"CSRF Failed: CSRF token missing or incorrect."}
E   assert 403 == 201
E    +  where 403 = <rest_framework.response.Response object at 0x7f7bd6453bd0>.status_code
E    +  and   201 = status.HTTP_201_CREATED

What's the correct way to test CSRF verification with DRF?

like image 530
Danilo Bargen Avatar asked Apr 20 '15 13:04

Danilo Bargen


People also ask

How do I pass CSRF token in REST API?

The CSRF token is stored in the client. The CSRF token is required for any later REST API calls. The client must send a valid token with every API request. The token is sent in a custom request HTTP header.

Does REST framework need CSRF token?

If you're using SessionAuthentication you'll need to include valid CSRF tokens for any POST , PUT , PATCH or DELETE operations. In order to make AJAX requests, you need to include CSRF token in the HTTP header, as described in the Django documentation.

How do I check Django REST framework?

For Django REST Framework to work on top of Django, you need to add rest_framework in INSTALLED_APPS, in settings.py. Bingo..!! Django REST Framework is successfully installed, one case use it in any app of Django.

How does Django handle CSRF?

The CSRF token is like an alphanumeric code or random secret value that's peculiar to that particular site. Hence, no other site has the same code. In Django, the token is set by CsrfViewMiddleware in the settings.py file. A hidden form field with a csrfmiddlewaretoken field is present in all outgoing requests.


1 Answers

EDIT

So, after researching this a bit, I discovered the following:

Django will not necessarily set a CSRF token in the header, unless it is rendering a template that explicitly has the csrf_token template tag included. This means that you need to request a page that renders a form with a csrf token, or you need to create a token-requesting view that is decorated with ensure_csrf_cookie.

Because the csrf token is unique per session, it is possible to create a generic token-setting view that looks something like the following:

from django.views.decorators.csrf import ensure_csrf_cookie

@ensure_csrf_cookie
def token_security(request):
    return HttpResponse()  # json or whatever

Then, any time you wish to POST to a CSRF protected endpoint and there is no CSRF token in the cookies, issue a GET against this view and it should set the cookie, which can then be used to POST.

Original answer below:


The following works in my tests (I am using factories to create User objects, but you could create them manually):

class TestLoginApi(APITestCase):
    def setUp(self):
        self.client = APIClient(enforce_csrf_checks=True)
        self.path = reverse("registration:login")
        self.user = UserFactory()

    def tearDown(self):
        self.client.logout()

    def _get_token(self, url, data):
        resp = self.client.get(url)
        data['csrfmiddlewaretoken'] = resp.cookies['csrftoken'].value
        return data

   def test_login(self):
        data = {'username': self.user.username,
                'password': PASSWORD}
        data = self._get_token(self.path, data)

        # This should log us in.
        # The client should re-use its cookies, but if we're using the
        # `requests` library or something, we'd have to re-use cookies manually.
        resp = self.client.post(self.path, data=data)
        self.assertEqual(resp.status_code, 200)
        etc.

If this is all done dynamically, you must also be sure that your view sets a cookie on the GET, because according to the Django docs (see the Warning), it will not be set automatically if you are not POSTing back from a template that had the {% csrf_token %} set.

If you need to set it, that looks something like this (in your DRF views.py):

from django.utils.decorators import method_decorator
from django.views.decorators.csrf import ensure_csrf_cookie

    @method_decorator(ensure_csrf_cookie)
    def get(self, request, *args, **kwargs):
        return SomeJson...

Finally, for my Django Rest Framework views, I had to make sure that the POSTs were csrf protected as well (but this does not look like a problem you are having):

from django.views.decorators.csrf import csrf_protect

    @method_decorator(csrf_protect)
    def post(self, request, *args, **kwargs):
        return SomeJson...
like image 111
erewok Avatar answered Oct 07 '22 15:10

erewok