Danilo Bargen
Danilo Bargen

Reputation: 19452

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?

Upvotes: 8

Views: 5429

Answers (1)

erewok
erewok

Reputation: 7835

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...

Upvotes: 11

Related Questions