Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django rest framework ignores missing csrf tokens with httpie

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?

like image 995
Visgean Skeloru Avatar asked Dec 30 '25 08:12

Visgean Skeloru


2 Answers

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 SessionAuthentication you'll need to include valid CSRF tokens for any POST, PUT, PATCH or DELETE operations. (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

like image 139
Jakub Roztocil Avatar answered Dec 31 '25 22:12

Jakub Roztocil


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.

like image 41
Kevin Brown-Silva Avatar answered Dec 31 '25 22:12

Kevin Brown-Silva