I am using httpie to play with my api written in django 1.7 and django rest framework 2.4. Today I was trying to delete an object:
$ http DELETE :8000/api/items/8/ --verbose
DELETE /api/items/8/ HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Length: 0
Host: 127.0.0.1:8000
User-Agent: HTTPie/0.8.0
HTTP/1.0 204 NO CONTENT
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Language: cs
Content-Length: 0
Date: Wed, 07 Jan 2015 21:47:06 GMT
Server: WSGIServer/0.1 Python/2.7.6
Vary: Accept, Accept-Language, Cookie
Which was successful even though it should require CSRF token. When I try to delete the object from Chrome with following code:
$.ajax({
type: "DELETE",
url: "http://127.0.0.1:8000/api/items/6/"
});
I get a following request:
DELETE /api/items/6/ HTTP/1.1
Host: 127.0.0.1:8000
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: */*
Origin: http://127.0.0.1:8000
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36
DNT: 1
Referer: http://127.0.0.1:8000/inventory
Accept-Encoding: gzip, deflate, sdch
Accept-Language: cs,en-US;q=0.8,en;q=0.6,es;q=0.4,pt;q=0.2,sk;q=0.2
Cookie: cc_csrf=bd9fbbc8f75cffa2e1e3d2c95c2185c5; _ga=GA1.1.2038400685.1386436341; __utma=96992031.2038400685.1386436341.1417173095.1417428975.79; __utmz=96992031.1409752584.3.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __zlcmid=MpdRtV3vZuf3D9; djdt=hide; sessionid=kiihjh6m77jm8v9ol7xrryip89sny55i; csrftoken=FtnnEWPLhMh0CAGMRMH77nB0AAno93uW
Response:
HTTP/1.0 403 FORBIDDEN
Date: Wed, 07 Jan 2015 21:57:40 GMT
Server: WSGIServer/0.1 Python/2.7.6
Vary: Accept, Accept-Language, Cookie
Content-Type: application/json
Content-Language: en
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
{"detail": "CSRF Failed: CSRF token missing or incorrect."}
My settings:
REST_FRAMEWORK = {
# Use hyperlinked styles by default.
# Only used if the `serializer_class` attribute is not set on a view.
'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.serializers.HyperlinkedModelSerializer',
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
'DATETIME_FORMAT': "%B %d, %Y"
}
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.locale.LocaleMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
)
So my question is: what is the difference between sending a DELETE request with JS ajax and with sending the request with http?
It's because the CSRF check is only performed when authenticated using SessionAuthentication, (i.e. using the sessionid cookie set by django.contrib.auth):
If you're using
SessionAuthenticationyou'll need to include valid CSRF tokens for anyPOST,PUT,PATCHorDELETEoperations. (source)
I assume you use another auth method for the HTTPie-sent request, and therefore the CSRF check doesn't get applied there.
https://github.com/tomchristie/django-rest-framework/blob/master/tests/test_authentication.py
When the request is made through the browser, it is including the sessionid token in the Cookie header. This header is automatically set by the browser, and includes other cookies that have been set (like djdt=hide by the Django Debug Toolbar).
Cookie: ...; sessionid=kiihjh6m77jm8v9ol7xrryip89sny55i; ...
Because of this, Django is authenticating the request automatically (just like it normally would), which is triggering the SessionAuthentication provided by Django REST framework. SessionAuthentication requires that the CSRF token is being validated, which is included in the csrftoken cookie and X-CSRFToken header, to ensure that nothing suspicious is happening.
This means that you must set the X-CSRFToken header when making your request in the browser. Django includes some useful snippets of code for popular libraries in their documentation on CSRF.
Now, when making requests through HTTPie, you are typically using a different form of authentication such as basic authentication. By default, Django REST framework enables BasicAuthentication and SessionAuhentication, unless you override them, and most of the documentation expects that you are using basic authentication.
HTTPie supports basic authentication through the -a username:password parameter. This would explain why you are not getting any permission issues when making the DELETE request, as without authentication you should be getting a 403 error. The DjangoModelPermissionsOrAnonReadOnly should not allow you to make the request you have provided without being authenticated.
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