From the Django docs:
Django’s CSRF protection requires that the request have a Referer header that matches the origin present in the Host header.
Using Ionic 3.9.2 (Angular 5.2.10), how does one satisfy this requirement when posting forms since one can't add the referer header? Disabling csrf protection is not an option. The same URL works via browser (even Ionic's in-app browser but the experience is just not desirable). I'm posting JSON data.
My code:
let headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded', 'HTTP_X_CSRFTOKEN': csrftoken});
return this.http.post(
hostUrl + "/auth/reset_password?org=" + org, body, {headers: headers}
).toPromise();
Related
I am trying to set up a Django API that receives POST requests with some JSON data and basically sends emails to a list of recipients. The logic is rather simple:
First I have the view for when I create a blog post. In the template, I include the csrf_token as specified on the Django Documentation. When I hit the submit button, behind the scene the create-post view, in addition to creating the post, makes a request (I am using the requests module) to the API which is charged with sending the emails. This is the piece of logic the sends the request to the API:
data = {
"title": new_post.title,
"summary": new_post.summary,
"link": var["BASE_URL"] + f"blog/post/{new_post.slug}"
}
csrf_token = get_token(request)
# print(csrf_token)
headers = {"X-CSRFToken": csrf_token}
requests.post(var["BASE_URL"] + "_api/send-notification/", json=data, headers=headers)
As you can see I am adding the X-CSRFToken to the headers which I generate through the get_token() method, as per the Django docs. However, the response in the API is a 403 Forbidden status CSRF Token not set.
I have checked the headers in the request and the token is indeed present. In addition, I have been providing other routes to the API and I have been using it for AJAX calls which again is very simple just follow the Django docs and they work perfectly well.
The problem seems to arise when I make the call from within the view, AJAX calls are handle by Javascript static files, and as I said they work fine.
I have thought that Django didn't allow the use of 2 CSRF tokens on the same page (one for the submit form and the other in the view by get_token()), but that's not the problem.
This is typically the error I get:
>>> Forbidden (CSRF cookie not set.): /_api/send-notification/
>>> "POST /_api/send-notification/ HTTP/1.1" 403 2864
I have read several similar questions on SO but they mostly involved using the csrf_exempt decorator, which in my opinion is not really a solution. It just gets rid of the CRSF token usefulness altogether.
Does anyone have any idea what might be causing this problem?
Thanks
Error tries to tell you that you need to add token into cookie storage like that:
cookies = {'csrftoken': csrf_token}
requests.post(var["BASE_URL"] + "_api/send-notification/", json=data, headers=headers, cookies=cookies)
The HTTP_X_CSRFTOKEN header does not match what is inside the csrftoken cookie.
How can I examine the cookie? Set-Cookie is not displayed in the Response header for Cross Domain requests.
I have already followed instructions found in:
CSRF with Django, React+Redux using Axios
Interestingly I found "X-CSRFTOKEN" translates to "HTTP_X_CSRFTOKEN" on the server request header.
Works fine in the development env under localhost (although I am using 2 different ports - one for django and the other my frontend).
UPDATE:
It seems the csrktoken cookie is not correctly set for cross domain rquests (although the browser displays it in the Request Header) so the X-CSRFTOKEN does not get sent.
I ended up adding an API call to return the current csrftoken using a GET request and then sending it back using the X-CSRFTOKEN header.
You haven't mentioned how you're getting the csrftoken from the server in the first place, so I'm assuming it's already present in your browser.
Along with the X-CSRFToken header, also include the cookies in the request using withCredentials: true.
I'm using the js-cookie library to get the csrftoken from the cookies.
import Cookies from 'js-cookie';
axios({
url: 'http://localhost:8000/graphql',
method: 'post',
withCredentials: true,
data: {
query: `
{
// Your query here
}
`
},
headers: {
"X-CSRFToken": Cookies.get('csrftoken')
}
})
Also add CORS_ALLOW_CREDENTIALS = True to your settings.py, assuming you are using django-cors-headers. Otherwise, the cookies won't be accepted.
You will have to make the X-CSRFTOKEN header accessible via the CORS Access-Control-Expose-Headers directive. Example:
Access-Control-Expose-Headers: X-CSRFTOKEN
This header has to be set by your API or web server, so that the browser will see it during the CORS preflight request.
I exposed API in Django backend. I want to get that API request from Angular 7. so I implemented the code that
this.HttpClient.get('Http://127.0.0.1:8000/myapp/posts')
.subscribe(
(data:any[]) => {
console.log(data)
}
)
But I'm getting an error that
Access to XMLHttpRequest at 'http://127.0.0.1:8000/myapp/posts' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
HttpErrorResponse {headers: HttpHeaders, status: 0, statusText: "Unknown Error", url: null, ok: false, …}error: ProgressEvent {isTrusted: true, lengthComputable: false, loaded: 0, total: 0, type: "error", …}headers: HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, headers: Map(0)}message: "Http failure response for (unknown url): 0 Unknown Error"name: "HttpErrorResponse"ok: falsestatus: 0statusText: "Unknown Error"url: null__proto__: HttpResponseBase
Could anyone suggest any solution to solve this error?
When sending HTTP requests from your front-end application, using the browser's fetch API, the Axios client or the jQuery $.ajax() method (a wrapper for the JavaScript XHR interface), to your back-end API built with Django REST framework the web browser will throw an error related to the Same Origin Policy.
Cross Origin Resource Sharing or CORS allows client applications to interface with APIs hosted on different domains by enabling modern web browsers to bypass the Same origin Policy which is enforced by default.
Ref how to do it here: https://www.techiediaries.com/django-cors/
you need to add a middleware file app/cors.py:
class CorsMiddleware(object):
def process_response(self, req, resp):
response["Access-Control-Allow-Origin"] = "*"
return response
This will add an Access-Control-Allow-Origin:* header to every Django request but before that you need to add it to the list of middleware classes:
MIDDLEWARE_CLASSES = (
#...
'app.CorsMiddleware'
)
Then install the django-cors-headers
Start by installing django-cors-headers using pip
pip install django-cors-headers
I am stuck with the following error message in the Chrome browser:
Failed to load http://localhost:5000/my_endpoint: Request header field
Access-Control-Allow-Origin is not allowed by
Access-Control-Allow-Headers in preflight response.
The browser is loading a webpage from a Vue.js frontend app with webpack etc. and vue-resource to perform HTTP requests to the REST backend.
The URL http://localhost:5000/my_endpoint is a HTTP GET/POST endpoint served by a python Flask application.
On the frontend Javascript I have these CORS settings:
import VueResource from 'vue-resource'
Vue.use(VueResource)
Vue.http.options.crossOrigin = true
Vue.http.headers.common['Access-Control-Allow-Origin'] = '*'
On the backend python code in the Flask app I have the following CORS configuration details:
#app.after_request
def add_header(response):
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Headers'] = 'Access-Control-Allow-Headers, Origin, X-Requested-With, Content-Type, Accept, Authorization'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS, HEAD'
response.headers['Access-Control-Expose-Headers'] = '*'
return response
When performing this HTTP POST request in the Javascript frontend:
this.$http.post("http://localhost:5000/my_endpoint", { my_custom_key: "my custom value"})//, {emulateJSON: true})
.then((response) => {
// do stuff
})
where { my_custom_key: "my custom value"} is the JSON body of the HTTP POST request, then in the Flask backend for some reason I see an HTTP OPTIONS request coming, cf. the Flask log:
127.0.0.1 - - [26/Jun/2018 21:45:53] "OPTIONS /ad HTTP/1.1" 200 -
There must be some sort of back/forth ceremony to honor before being able to retrieve the JSON data from the backend, but I am lost in these details.
On the internet I've seen all sorts of explanations, and I've been playing a bit with the vue-resource configuration details like:
adding/removing {emulateJSON: true} to the HTTP POST request
adding/removing Vue.http.options.xhr = { withCredentials : true }; to the configuration of Vue.js
but I am not able retrieve the JSON data coming from the backend.
Searching for "CORS" on the Vue.js documentation or on https://github.com/pagekit/vue-resource does not give any information on how to fix these issues with Cross-origin resource sharing (CORS).
How do I get a Vue.js frontend working with a Flask backend on these CORS issues?
The solution was to remove from the frontend: Vue.http.headers.common['Access-Control-Allow-Origin'] = '*' (which actually does not make sense as it's an HTTP header that usually goes into the response from the backend).
Also to clean up a bit more the backend I found out that response.headers['Access-Control-Expose-Headers'] = '*' was not needed. Probably the other HTTP headers could be more slim, but for now I keep them as they are.
I have a very simple form that I've added the uploader to. When I invoke the uploader, django returns
{"detail":"CSRF Failed: CSRF token missing or incorrect."}
This is the uploader:
var ul = new Uploader(
{
label:"Programmed uploader",
multiple:false,
uploadOnSelect:true,
url:Environment.apiRoot + "upload/",
headers:{
"Accept" : "application/json",
"X-CSRFToken" : dojo.cookie("csrftoken")
}
}).placeAt(form);
I created simple "test" button that invokes a function that performs the same post.
new Button({
name:"Cancel2",
//id:"Cancel",
label:"Cancel" ,
placement:"secondary",
onClick:lang.hitch(this,function(event){
this._testpost()
})
}).placeAt(form);
This is the relavent header from the uploader post
Cookie djdt=hide; csrftoken=WwlARc9OUevblKfgNEDU2Ae4eT9z0kos;sessionid=du37rjyam6v69mw0bgctkbw708xlvc5g
This is the _testpost()
_testpost: function (){
xhr.post({
url: Environment.apiRoot + "upload/",
handleAs: "json",
postData: json.stringify(data),
headers: {
"Content-Type": "application/json",
"Accept" : "application/json",
"X-CSRFToken" : dojo.cookie("csrftoken")
},
loadingMessage: "Submitting form..."
}).then(
lang.hitch(this,function(result) {
form = t._f_form;
dojo.destroy(form);
this._float.destroyRecursive();
alert(result['result_text']);
result['message'] = "Update Request Accepted";
}),lang.hitch(this, function(err){
form = t._f_form;
dojo.destroy(form);
this._float.destroyRecursive();
topic.publish("/application/message","An error occurred.");
}));
this is the relevant header from invoking the _testpost function
Cookie djdt=hide; csrftoken=WwlARc9OUevblKfgNEDU2Ae4eT9z0kos;sessionid=du37rjyam6v69mw0bgctkbw708xlvc5g
X-CSRFToken WwlARc9OUevblKfgNEDU2Ae4eT9z0kos
The key difference being that in the _testpost the X-CSRFToken is put into the header, but on the Uploader post, I don't have any means to put in an X-CSRFToken (my headers attribute seems to just be ignored - i tried it to see if I could get this to work)
Is there any way to get additional headers into the Uploader
Unfortunately, dojox.form.Uploader does not allow headers to be added.
There are a couple options. It sounds like you have access to the csrf token and could append it to the url. Another option may be to provide the csrf token as a cookie and it should be sent with XHR and Flash request.
What I have done (and i'm not sure this is correct), within the django view, disabled csrf checking, and then pull the csrf value out of the header and compare it against the csrf value that is kept in the session record on the server.
you may use dojo.aspect to add the headers to the dojox.form.Uploader.
In case you are using HTML5 upload "plugin", that looks like since you have left the default, you may use something like:
aspect.after(ul, "createXhr", function(xhr) {
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("X-CSRFToken", dojo.cookie("csrftoken"));
return xhr;
});
Add this just after you create the Uploader. Also remember to require dojo/aspect.
Notice that this is a bit of a hack and prone to breakage if some change happen in dojox.form.Uploader structure (e.g. they update it to use dojo.promise or other fixes). Also it's implied that this works only for HTML5 plugin, but you may extend the code in the same way to cope for other plugins by inspecting ul.uploadType and make the change specific for that plugin.
This solution works up to and including dojo version 1.12. In 2017 the above announced breakage did happen an this does not work anymore with version of dojo from 1.13 and upward.