Reputation: 19452
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
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