CSRF validation works from Postman but not React/Axios - python

It's honestly a catastrophic blow to the ego I haven't already figured this out--spent 6 hours so far...
I have a React app running off a Django Rest Framework backend. For the password reset functionality I am using Django's builtin view (auth_views.PasswordResetView). I have a form in React that accepts an email and sends a post request to reset the users password. The way I send the data in postman is:
Url: http://192.168.0.85:8000/reset_password
Body: {'email': 'blabla#bla.com'}
Headers: {'X-CSRFToken': OGH9iUEtGPqntMYifQ5kiin2ufV9tK39tbp9Wmh6tLST0DXCXSkY8mOvyq5AjjnZ}
...and it works like a charm! Until I try to replicate the same exact call using axios in React:
axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
axios.post('http://192.168.0.85:8000/reset_password',{'email': 'blabla#bla.com'},{headers}))
...this results in a 403 error with this message from the backend:
WARNING:django.security.csrf:Forbidden (CSRF cookie not set.): /reset_password
I've spent 6 hours googling and experimenting and can't figure out for the life of me why the Axios request would be any different than the Postman one. This problem is particularly infuriating because #csrf_exempt won't work on the django builtin views.
Here are the headers from a successful request in Postman:
X-CSRFToken: OGH9iUEtGPqntMYifQ5kiin2ufV9tK39tbp9Wmh6tLST0DXCXSkY8mOvyq5AjjnZ
User-Agent: PostmanRuntime/7.28.0
Accept: */*
Postman-Token: 91cbbfe7-b434-45fc-97ec-7d442c091070
Host: 192.168.0.85:8000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 33
Cookie: csrftoken=gawXPEZQfN4qC4aKPhiIvK5qpjvkp4WCVFeXt6Ct2JwW9V94xjxmlOwTtuFLfDgs
...and from the failed request using Axios from React:
baseURL: "http://192.168.0.85:8000"
data: "{\"email\":\"blabla#bla.com\"}"
headers:
Accept: "*/*"
Authorization: ""
Content-Type: "application/json"
X-CSRFToken: "udd4yAGCJuTxw95kCdHYwwEQmGwu1bwxqEQjZhI56v1qWY143S4IPCrUSvk9xkV8"
__proto__: Object
maxBodyLength: -1
maxContentLength: -1
method: "post"
timeout: 0
transformRequest: [ƒ]
transformResponse: [ƒ]
url: "/reset_password"
validateStatus: ƒ validateStatus(status)
xsrfCookieName: "csrftoken"
xsrfHeaderName: "X-CSRFToken"
__proto__: Object
isAxiosError: true

Does this answer your question, https://stackoverflow.com/a/66550363/7838574 ?
With emphasis on the settings.py portion. That is what tripped me up about a week ago.
Honestly great job keeping at this and asking for help! Programming can be insanely frustrating and once the hurdle is behind you very rewarding!

Related

Odd: Twitter API v1.1 returning Error(46) "'The Twitter REST API v1 is no longer active." from a VALID 1.1 endpoint. Any body else?

When calling the twitter api v1.1 endpoint:
https://upload.twitter.com/1.1/media/uplaod.json
I get the following error:
message: The Twitter REST API v1 is no longer active. Please migrate to API
v1.1. https://dev.twitter.com/docs/api/1.1/overview.
code: 64
Full request made via python-requests with requests-oauthlib:
POST https://upload.twitter.com/1.1/media/uplaod.json
Content-Length: 25566
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.4.3 CPython/2.7.2 Windows/post2008Server
Connection: keep-alive
Content-Type: multipart/form-data; boundary=57ae35dd03714226a367e5a8268f9c5b
Authorization: OAuth oauth_nonce="<censored>", oauth_timestamp="1424492683", oauth_version="1.0", oauth_signature_method="HMAC-SHA1", oauth_consumer_key="<censored>", oauth_token="<censored>", oauth_signature="<censored>"
--57ae35dd03714226a367e5a8268f9c5b
Content-Disposition: form-data; name="media[]"; filename="4725ec4acf23d7a9a7fd92337f2b4713f746c7df.jpg"
Content-Type: application/octet-stream
<censored...binary...data>
--57ae35dd03714226a367e5a8268f9c5b--
This was working earlier today. Can somebody confirm it's a twitter issue and i'm not going mad?
Seems other endpoints such as https://api.twitter.com/1.1/statuses/home_timeline.json are working fine.
Clicking this link with chrome gets the error:https://upload.twitter.com/1.1/media/uplaod.json
It's Twitter, not me...
It seems you are calling wrong URL - just typing error.
Instead
https://upload.twitter.com/1.1/media/uplaod.json
you should call:
https://upload.twitter.com/1.1/media/upload.json

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?
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
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.
EDITED & UPDATED
OK apart from the explanations others already mentioned, after all that, we can conclude the reason why Httpie allows your DELETE but not Javascript:
1) Since you have actually disabled your authentication, in theory all METHODS will be allowed from individual HTTP call, hence your Httpie works (just like when you use Curl), because Restframework doesn't require you to.
2) Ajax call from Javascript, however is slightly different because you're using your browser console to do the call, which is in fact within the browser session. Further to this, your cookie has stored your previous GET's CSRF token which afterward when you perform the Ajax call, the CSRF token has been extracted from Django/Restframework which doesn't MATCH (because CSRF token will be automatically re-generated on each request. So this is a matter of INCORRECT rather than *MISSING** token.
Hence like in my above comment, removing your browser's cookie / using a Private session has indeed resolved the issue, and successfully allowed you to perform Ajax style DELETE.
Hope this helps, and thanks for everyone's guidance and hints leading me to this conclusion.

How to send/receive URL parameters in a HTTP POST correctly?

I am using cakephp 2.4.5. I want to send a HTTP POST with URL parameters. I am using python 2.7 request module to send the HTTP POST. Please assume the payload is structured correctly as I have tested that part.
URL_post = http://127.0.0.1/webroot/TestFunc?identity_number=S111A/post
r = requests.post(URL_post, payload)
On the cakephp side, the controller looks something like this;
public function TestFunc($id=null)
{
$identity_number = $this->request->query['identity_number'];
$this->request->data['Model']['associated_id']=$identity_number;
$this->Model->saveAll($this->request->data, array('deep' => true));
}
I have tested that the query is not received correctly. However, if I am not using HTTP POST and just throwing in a normal URL, the query can be received correctly.
What have I done wrong?
The query part of the url is sent correctly:
import requests
requests.post('http://localhost/webroot/TestFunc?identity_number=S111A/post',
{'Model': 'data'})
The Request
POST /webroot/TestFunc?identity_number=S111A/post HTTP/1.1
Host: localhost
User-Agent: python-requests/2.2.1 CPython/3.4 Linux/3.2
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
Model=data
You could also make the requests using params:
requests.post('http://localhost/webroot/TestFunc',
data={'Model': 'data'},
params={'identity_number': 'S111A/post'})
The only difference is that S111A/post is sent as S111A%2Fpost (the same url in the end).
Look at http://docs.python-requests.org/en/latest/user/quickstart/#passing-parameters-in-urls.
payload = {"identity_number": "S111A/post"}
URL_post = "http://127.0.0.1/webroot/TestFunc"
req = requests.post(URL_post, params=payload)
print(req.status_code)

Get full raw http request (complete with headers and body)

In a appengine project im trying to get the whole http request inside an webapp2.RequestHandler:
class ConnectedHandler(webapp2.RequestHandler):
def post(self):
logging.info("Someone connected: " + self.request.get('from'))
# How to get the raw http request from self.request?
Having looked through the documentation im begining to think its not possible
The result im looking for is something like this (What i would call a http request anyways):
POST /6473924464345088 HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate, compress
Content-Type: application/json; charset=utf-8
Host: localhost:10083
User-Agent: HTTPie/0.3.0
{
"u": "a"
}
Edit: Updated the example
Is there another cleaver way to access this data when using webapp2 ?
This should get you exactly that:
class MainPage(webapp2.RequestHandler):
def post(self):
self.response.write('Just received:\n\n' + str(self.request))

403 FORBIDDEN in django agon-rating

I have 403 FORBIDDEN error in agon-ratings plugin when submit rating.
I have read the doc. But csrf token exists in header:
Request Headersview source
Accept */*
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Cache-Control no-cache
Connection keep-alive
Content-Length 22
Content-Type application/x-www-form-urlencoded; charset=UTF-8
Cookie csrftoken=6C7zHmrBufWbiYeTXwRkCWC9hDfdxGoW; sessionid=4d6b6977721fcb97f6903d0aaab5e632
Host localhost:8000
Pragma no-cache
Referer http://localhost:8000/news/40/asdas/
User-Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:23.0) Gecko/20100101 Firefox/23.0
X-Requested-With XMLHttpRequest
Any help with this issue will be appreciated.
Thanks in advance
I guess you are doing a post via ajax request, if thats correct than you need to send the csrf token as part of POST data or via the X-CSRFToken header.
I dont see any of that in the request header you posted.
Django docs you linked have a working example about how to do this (and if you use jQuery its mostly a copy and paste job)
You are using POST method. And with POST method, you need to write {% csrf_token %} in HTML inside form element or pass csrftoken in ajax request i.e https://docs.djangoproject.com/en/dev/ref/contrib/csrf/.
It is compulsory when you write CSRFMiddleware and csrf context processor in your django settings.

Categories

Resources