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?
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.
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.
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.
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.
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...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With