Django REST framework no CSRF token with runserver but with runserver_plus - django-rest-framework

Using django-extensions' "runserver_plus" command, I've no problem with CSRF token. The token is defined in cookies as expected in server responses after login and I use it to sent it back to server in HTTP headers during unsafe HTTP requests (POST, PUT, etc...)
But for some reasons, I have to not use "runserver_plus" anymore and switch back to legacy "runserver" command. And since, the CSRF token I get from server after login does not work anymore. I got the error CSRF Failed: CSRF token missing or incorrect.. There is no other changes in the code. Just switch from runserver_plus to legacy runserver.
I really don't understand why. It's a mistake for me.
Of course, REST framework authentication classe is SessionAuthentication
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
),
}
Is someone have any idea ?

I've found why. It's because I use a underscore in HTTP header name for setting the CSRFTOKEN : I do this in javascript : header['X_CSRFTOKEN'] = mytoken. But because production servers strip underscored header name runserver do so. runserver_plus does not have the same behavior and allow underscored http header names.
Replacing my javascript by header['X-CSRFTOKEN'] = mytoken (dash instead of underscore) make it work.

Related

Postman request receiving cookies but Postman not detecting them

I've recently started using Postman and I am testing an API where I get a CSRF token and then login but I always get a CSRF token mismatch. I am including X-XSRF-TOKEN header but I think the issue is around the cookies not being stored correctly.
I am calling a Laravel Sanctum endpoint to get a CSRF token and when I look in the in the console I can see Set-Cookie response headers
However, when I look in the cookies tab it says no cookies were received from the server
However, when I look at the cookies store they are listed for my test domain (home.local)
Due to this issue, when I send a request to the login, the session cookies are not sent in the request as shown in the console on the request to the login endpoint
I can do this fine using Insomnia.Rest client so I know the API is working as expected - I am however trying to replace Insomnia with Postman.
I've tried Google, but I've only found some bugs that were introduced that seemed to cause something similar back in 2016
Update
I managed to Postman working with production fine using a pre-request script to get the CSRF token and set the environment variable using the below:
const url = pm.environment.get('base_url');
const referer = pm.environment.get('Referer');
pm.sendRequest({
url: `${url}/sanctum/csrf-cookie`,
method: 'GET',
}, function (err, response, {cookies}) {
if (!err) {
console.log("cookies", cookies);
pm.environment.set('xsrf_token', cookies.get('XSRF-TOKEN'))
}
});
Although this worked on production and successfully did the POST request, on my local dev PC, I was still getting the CSRF mismatch.
Although the request/response looked the same between dev/and prod I for some reason had the idea to change my dev URL from my-app.home.local to my-app.home.com and now the cookies are received and send in the next request to login without getting a CSRF token mismatch.
There's clearly an issue with postman here but not sure if it's something I'm doing or a bug in Postman. Does .local mean something different?

Laravel Sanctum CSRF Token Mismatch using Thunder Cilent (Testing API)

CSRF Token Mismatch when using Thunder Cilent to test Laravel Sanctum API. Works fine on app, just not when testing API outside app.
Trying to test API with Laravel Sanctum with Thunder Cilent (Think Postman but VSCode extension). However, despite setting the xsrf-token and Cookie, I'm always getting CSRF token mismatch.
Note logging on works fine in the app, it's only the api testing with thundercilent that's failing.
Getting the CSRF Token
GET http://localhost:8000/sanctum/csrf-cookie
Response Cookies
xsrf-token: eyJpdiI6ImN6Q3JLVEQrYnhXVXhyVWFQWC9YQlE9PSIsInZhbHVlIjoia2F2aTNjNDU2cTZURHRSSTN5Ny9ETnFJMGZoN0I2dmZ3bTA0UEZ6UjhzdCtCRjRPam9OSW5TWVkzYzAvMTQ0ZEp6b2JvYVdhRWg2TGsrejlkcnYzTGY3eGNFcTRGN253dUUxZjE3YXJBSFlVUHk4aGM5RmVYRWF6UFY2ZGRnYUEiLCJtYWMiOiI4OWU3OGI3MzQ3ZTdiNTNiZDQ2Yjg0ZDE3YWNiYmVhNDQ1NTI0MmI3MTY1NjdlZGI5ZGJlZDJlN2Q5NTc0ZjRhIiwidGFnIjoiIn0%3D
laravel_session: eyJpdiI6IjV6VTV4di9IMXNST1ZvNVh0K1pZelE9PSIsInZhbHVlIjoiTSsycEVWdjJ1VTc4dU81TVNJWTJ4aTRHOE81WTVHVW1OeU55OEt3cVU3bHc5N090dEdPQy9yZGJsamhOaDUzaFZmZVp0Z2FTeGp4UWJyVFVmSDdnVytTNS9SZTF5c0daak9EZ1I1V0w3aWpjTnVESWtIRmR2QzNGZ1VqWlZHZ2oiLCJtYWMiOiI0Mzg4NGI4MTc5MGQ1MDE1NTUxY2VmNGRmNGFkNjUyYmI1MjUwMTJiODQ4NmY4M2E5OTRlZGRlNTM3NjAzNTg1IiwidGFnIjoiIn0%3D
Logging in
POST http://localhost:8000/login
Body: {
"email": "example#example.com",
"password": "password"
}
Raw Headers:
User-Agent: Thunder Client (https://www.thunderclient.com)
Accept: application/json
Referer: http://localhost:3000
xsrf-token: eyJpdiI6ImN6Q3JLVEQrYnhXVXhyVWFQWC9YQlE9PSIsInZhbHVlIjoia2F2aTNjNDU2cTZURHRSSTN5Ny9ETnFJMGZoN0I2dmZ3bTA0UEZ6UjhzdCtCRjRPam9OSW5TWVkzYzAvMTQ0ZEp6b2JvYVdhRWg2TGsrejlkcnYzTGY3eGNFcTRGN253dUUxZjE3YXJBSFlVUHk4aGM5RmVYRWF6UFY2ZGRnYUEiLCJtYWMiOiI4OWU3OGI3MzQ3ZTdiNTNiZDQ2Yjg0ZDE3YWNiYmVhNDQ1NTI0MmI3MTY1NjdlZGI5ZGJlZDJlN2Q5NTc0ZjRhIiwidGFnIjoiIn0%3D
Cookie: XSRF-TOKEN=eyJpdiI6ImN6Q3JLVEQrYnhXVXhyVWFQWC9YQlE9PSIsInZhbHVlIjoia2F2aTNjNDU2cTZURHRSSTN5Ny9ETnFJMGZoN0I2dmZ3bTA0UEZ6UjhzdCtCRjRPam9OSW5TWVkzYzAvMTQ0ZEp6b2JvYVdhRWg2TGsrejlkcnYzTGY3eGNFcTRGN253dUUxZjE3YXJBSFlVUHk4aGM5RmVYRWF6UFY2ZGRnYUEiLCJtYWMiOiI4OWU3OGI3MzQ3ZTdiNTNiZDQ2Yjg0ZDE3YWNiYmVhNDQ1NTI0MmI3MTY1NjdlZGI5ZGJlZDJlN2Q5NTc0ZjRhIiwidGFnIjoiIn0;laravel_session=eyJpdiI6IjV6VTV4di9IMXNST1ZvNVh0K1pZelE9PSIsInZhbHVlIjoiTSsycEVWdjJ1VTc4dU81TVNJWTJ4aTRHOE81WTVHVW1OeU55OEt3cVU3bHc5N090dEdPQy9yZGJsamhOaDUzaFZmZVp0Z2FTeGp4UWJyVFVmSDdnVytTNS9SZTF5c0daak9EZ1I1V0w3aWpjTnVESWtIRmR2QzNGZ1VqWlZHZ2oiLCJtYWMiOiI0Mzg4NGI4MTc5MGQ1MDE1NTUxY2VmNGRmNGFkNjUyYmI1MjUwMTJiODQ4NmY4M2E5OTRlZGRlNTM3NjAzNTg1IiwidGFnIjoiIn0%3D;
I copied how this SO Postman example, but it's not working at all. Thunder Cilent doesn't have pre-run scripts so I can't add cookies that way.
Open thunder client, switch to Env and create an environment.
Click on the options button of your collection and choose settings.
Add a test to your collection by navigating to the Tests tab, then select Set Env Variable, set query to cookie.xsrf-token and value to {{XSRF-TOKEN}}
In the Headers tab, add an header named X-XSRF-TOKEN with value {{XSRF-TOKEN | urlDecode}}.
Create a request to /sanctum/csrf-cookie to refresh CSRF Token if it expires
This should fix CSRF Token.
Consult docs for more information: https://github.com/rangav/thunder-client-support
Decided just to use token based auth for API testing instead. Much easier then messing around with CSRF cookies.
Notes for my future self.
When testing the Token based auth, you need to set the accept to application/json otherwise it won't work.
If using collections, don't delete the Accept header in the request, just untick the box. Otherwise the request would override the collection header with something else and not work.

Heroku CSRF and POST httpRequest

I have a web crawler on Heroku and I'm trying to call the script from a POST request on Parse Cloud Code httpRequest but I receive a 403 forbidden response basically telling me the Referer Header didn't pass. How can I get past this?
Django's CSRF protection tests the Referer header: see https://docs.djangoproject.com/es/1.9/ref/csrf/#how-it-works. Browsers typically send that header to indicate the page that originated a request, but programmatic user agents don't (cURL, Python requests, and presumably Parse.Cloud.httpRequest) without being told to do so.
To add custom headers to a Parse request, see: Parse.Cloud.httpRequest call with HTTP request header (note the headers object).
That said, you also need to make sure you have a way to get the CSRF token to begin with, and include it either in a XCSRF-Token header or a form field (unclear from your question whether you are doing that).

Drupal services/cors api not accepting CSRF token

I am using polymer to send ajax requests to my Drupal services api.
I send a POST to login and then a POST to create a node. When I login I am given a token which I store and pass to the next request.
I am monitoring the the requests and responses with Charles, the token is being sent, the cookie is being set and passed on the 2nd POST but I get an "Unauthorized : CSRF validation failed" response.
When I send the request with Postman It works like a dream but for some reason it doesn't validate when sent with my app.
I have checked the token being set matches the one being sent and the only difference I've noticed is that when it's being sent again there is a prefix of ga_; something to do with google analytics?
The expiry of the token is a month away the token matches what is returned at login and is being sent correctly. The header accepts X-CSRF-Token in the Access-Control-Allow-Headers.
My CORS module code is:
api/*|<mirror>|GET, POST, PUT, OPTIONS|Authorization, Origin, Content-Type, X-CSRF-Token|true
If any body has a similar issue, mine was caused by a couple of things, running Drupal and my app in the same browser causing all kinds or cookie conflicts and when passing parameters to my function that computes my request, if there were any parameters that were not used it breaks.
Hope this helps someone.

How to handle CSRF in preflighted CORS POST request in django?

I am trying to make POST request via AJAX from abc.com to URL from xyz.com (which is a Django application).
I am getting CSRF token by making a GET request to a URL on xyz.com, but the token changes when an OPTIONS request is made to xyz.com in the preflighted request.
Is there any way to get the response of OPTIONS request in the preflighted request ?
Note:
I am following instructions from following sources :
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS
http://www.html5rocks.com/en/tutorials/cors/
Django CSRF protection will allow OPTIONS requests, so no problem with the first stage:
https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#how-it-works
If I understand correctly, you then want the next request (e.g. a cross-domain POST) to be allowed through. For this to work and get past Django's CSRF protection, the request must send a CSRF token (in POST data or in header for AJAX) and a matching CSRF cookie.
Now, cross-domain restrictions make it impossible for abc.com to set or read a cookie for xyz.com, whether from javascript or from a server side response. Therefore, this approach is impossible.
Instead you will have to apply #csrf_exempt to the view. This would allow any site to post to it. Therefore, you'll need to build in some other protection to the view. You are, of course, on your own in checking the security of your protection. Remember that 'Referer' and 'Origin' headers can easily be forged with something as basic as curl.
See django-cors-headers, you may find it how it works more suitable to solve your problem:
https://github.com/ottoyiu/django-cors-headers/
Django-rest-framework recommends http://www.django-rest-framework.org/topics/ajax-csrf-cors

Resources