only OPTIONS request is sent with a 200 response - django

My situation is a bit similar to this question but it differs a bit nor there is an answer.
My backend is python and front-end is Angular. Live sever is Ngnix/Unix while dev is Windows. Every request just sends OPTIONS request, with successful response of 200 but then GET/POST are not followed. It was working perfectly fine and only on production sever is it not working. There are no CORS errors in the console and backend is debug= True for checking purposes but not problems so far cos obviously no get/post is being made.
On development machine, all is working. Previous team had added a custom header 'language':
const clonedRequest = req.clone({ headers: req.headers.set('Language', lang) });
which I noticed is never sent when connected to the live one. In the development setup, I see the following headers:
Accept application/json
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Connection keep-alive
Content-Length 21
Content-Type application/json
Host 127.0.0.1:9000
Language en
Origin http://localhost:4800
Referer http://localhost:4800/start/forgot-pwd
When connecting to the production from front end (Development or in production), the Language header is not being sent but I doubt it has issues. Regardless, my CORS on the backend looks like this:
CORS_ALLOW_HEADERS = (
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'language',
'x-requested-with',
)

You need to whitelist specific origins.
Using corsheaders such as:
CORS_ORIGIN_WHITELIST = [
'localhost:4800',
'127.0.0.1:4800'
]
You also need to add to you middlewares right after SecurityMiddleware
'corsheaders.middleware.CorsMiddleware',
And you also need to add 'corsheaders', to you INSTALLED_APPS.
Furthermore, you can simply have CORS_ALLOW_CREDENTIALS = True set and in addition to what I listed above to have CORS working.

Related

The CORS header is present on PUT requests but not on OPTIONS requests

I have a Google Cloud Storage bucket with the following CORS configuration:
[
{
"origin": ["http://localhost:8080"],
"responseHeader": [
"Content-Type",
"Access-Control-Allow-Origin",
"Origin"],
"method": ["GET", "HEAD", "DELETE", "POST", "PUT", "OPTIONS"],
"maxAgeSeconds": 3600
}
]
I am generating a signed URL with the following code:
let bucket = storage.bucket(bucketName);
let file = bucket.file(key);
const options = {
version: "v4",
action: "write",
expires: Date.now() + 15 * 60 * 1000, // 15 minutes
contentType: "application/zip"
};
let url = await file.getSignedUrl(options))[0];
For my requests I am using the following headers:
Origin: http://localhost:8080
Content-Type: application/zip
When I try using a PUT request to upload the data everything works fine and I get the Access-Control-Allow-Origin header containing my Origin. But when I do a OPTIONS request with the exact same headers it fails to return the Access-Control-Allow-Origin header. I have tried many alterations to my CORS config but none have worked like:
Changing the origins to *
The different changes described in Stackoverflow it's answer and comments.
The different changes described in GitHub Google Storage API Issues
I solved my own problem with some help from my colleagues, when I was testing the function in Postman the CORS header was not sent as a response to the OPTIONS request because the request was missing the Access-Control-Request-Method header. When I added this header it worked fine.
Notice that as stated on the public documentation you shouldn't specify OPTIONS in your CORS configuration and notice that Cloud Storage only supports DELETE, GET, HEAD, POST, PUT for the XML API and DELETE, GET, HEAD, PATCH, POST, PUT for the JSON API. So, I believe that what you are experiencing with the OPTIONS method should be expected behavior.

Angular 9 front/corsheaders django 3 back - appending the CORS headers to the request

having the problem to find a way how to configure properly front end app in Angular 9 consuming back REST webservice. On the remote server with the static IP i run my angular dev server with (i know it is only for dev purposes but i want to get rid of this error first)
ng serve --host 0.0.0.0 --port 80
so i can access it from the outside. then i have my django app also with the dev server at
127.0.0.1:8000
In the angular service.ts file i am trying to inject the header
export class FlightService {
httpOptions = {
headers: new HttpHeaders({
'Access-Control-Request-Method':'GET',
'Access-Control-Request-Headers':'origin, x-requested-with, accept'
})
};
private endpoint = 'http://127.0.0.1:8000/services';
constructor(private http: HttpClient) { }
//GET All Services
getAllServices(): Observable<any>{
return this.http.get(this.endpoint, this.httpOptions)
}
}
In the settings.py of my django backend i have put at the top the corsheaders module:
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
and allowed all the host (after trying many things with whitelist which none of worked:
CORS_ALLOW_CREDENTIALS = False
CORS_ALLOW_METHODS = [
# 'DELETE',
'GET',
'OPTIONS'
#'PATCH',
#'POST',
#'PUT',
]
CORS_ALLOW_HEADERS = [
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
'x-forwarded-for'
]
But I fail all the time. After i added the httpOptions i get the error in the browser:
http.js:2403 Refused to set unsafe header "Access-Control-Request-Method"
and as well:
Refused to set unsafe header "Access-Control-Request-Headers"
So at least i know the Angular is trying to append it.
Please help, how to approach it? I guess the problem is on the client side. I also tried to set up Nginx with a proxy to Angular dev, but also fail to append the headers on the Nginx configuration.
These CORS headers are not for you to set, the browser will do it automatically if it detects that you are trying to access cross domain resources.

missing token ‘access-control-allow-origin’ in CORS header ‘Access-Control-Allow-Headers’ from CORS preflight channel

I'm trying to connect my a React Application to a Django Server. The React application is running on http://127.0.0.1:3000/
The response headers has Access-Control-Allow-Origin: http://127.0.0.1:3000 set, yet I am still seeing the error.
I am currently using Django's corsheaders package as recommended everywhere. Decent example of the recommendation How can I enable CORS on Django REST Framework.
But I have also tried custom middleware at this point.
My Django Settings.py file contains the following
MIDDLEWARE = [
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
Django is on 8000, React on 3000
CORS_ORIGIN_WHITELIST = [
'http://127.0.0.1:3000',
'http://localhost:3000',
'http://127.0.0.1:8000',
'http://localhost:8000',
]
My request in react looks like this. (It works when I run it directly, not through the browser)
const fetch = require('node-fetch')
const response = await fetch(
url,
{
json: true,
method: 'GET',
headers: {
'Access-Control-Allow-Origin': '*',
Accept: '*/*',
'Content-Type': 'application/json'
}
}
)
Again, it is so strange that I am making the request from http://127.0.0.1:3000 and the response headers has Access-Control-Allow-Origin: http://127.0.0.1:3000 but for some reason it is still failing.
Oh the error message in the browsers console is
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:8000/query/?date=2019-10-25. (Reason: missing token ‘access-control-allow-origin’ in CORS header ‘Access-Control-Allow-Headers’ from CORS preflight channel).
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:8000/query/?date=2019-10-25. (Reason: CORS request did not succeed).
Any help would be awesome! Thanks
#sideshowbarker figured it out. It was because I was sending headers in my request.
Changing
const fetch = require('node-fetch')
const response = await fetch(
url,
{
json: true,
method: 'GET',
headers: {
'Access-Control-Allow-Origin': '*',
Accept: '*/*',
'Content-Type': 'application/json'
}
}
)
to
const fetch = require('node-fetch')
const response = await fetch(
url,
{
json: true,
method: 'GET'
}
)
Immediately fixed the issue! Thank you!

Browser makes OPTIONS request to django for CORS request, but no POST

I'm trying to submit a form from a react application, via post, to a django server on a different origin.
The browser sends an OPTIONS request, which the cors middleware on the server responds to with a 200, and the following information:
HTTP/1.1 200 OK
Date: Mon, 08 Apr 2019 16:34:38 GMT
Server: WSGIServer/0.2 CPython/3.7.2
Content-Type: text/html; charset=utf-8
Content-Length: 0
Vary: Origin
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with
Access-Control-Allow-Methods: DELETE, GET, OPTIONS, PATCH, POST, PUT
Access-Control-Max-Age: 86400
Connection: keep-alive
But the browser never subsequently makes a POST request. It shows no errors in the console...
Try to install django-cors-headers (https://pypi.org/project/django-cors-headers/) app and just add CORS_ORIGIN_ALLOW_ALL = True to you django settings file. It is the simplest way to fix you issue and this app gives you a lot of CORS customization options.
Or you can write custom middleware and add CORS headers for each response.
Otherwise you could add CORS headers config to you web-server (nginx, apache, etc.).
go to inspect element for request response, and check if any "headers are not allowed" message there.
If so, add that header to CORS_ALLOW_HEADER = ['that-header'] in django settings.py
In my case, cache-control header was not allowed. So I added it, it worked.
It turns out you have to add Cache-Control to CORS_ALLOW_HEADERS to get it to work correctly. Some browsers don't handle setting it to '*'. Here's my setup:
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_METHODS = [
"DELETE",
"GET",
"OPTIONS",
"PATCH",
"POST",
"PUT",
]
CORS_ALLOW_HEADERS = [
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
"cache-control",
"pragma",
]

Django, Heroku, boto: direct file upload to Google cloud storage

In Django projects deployed on Heroku, I used to upload files to Google cloud storage via boto. However, recently I have to upload large files which will cause Heroku timeout.
I am following Heroku's documentation about direct file upload to S3, and customizing as follows:
Python:
conn = boto.connect_gs(gs_access_key_id=GS_ACCESS_KEY,
gs_secret_access_key=GS_SECRET_KEY)
presignedUrl = conn.generate_url(expires_in=3600, method='PUT', bucket=<bucketName>, key=<fileName>, force_http=True)
JS:
url = 'https://<bucketName>.storage.googleapis.com/<fileName>?Signature=...&Expires=1471451569&GoogleAccessId=...'; // "presignUrl"
postData = new FormData();
postData.append(...);
...
$.ajax({
url: url,
type: 'PUT',
data: postData,
processData: false,
contentType: false,
});
I got the following error message:
XMLHttpRequest cannot load http:/... Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access.
EDIT:
The output of gsutil cors get gs://<bucketName>:
[{"maxAgeSeconds": 3600, "method": ["GET", "POST", "HEAD", "DELETE", "PUT"], "origin": ["*"], "responseHeader": ["Content-Type"]}]
It seems the CORS is OK. So, how do I solve the problem? Thanks.
EDIT 2:
The header of the OPTION request from Firefox:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.5,en;q=0.3
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: <bucketName>.storage.googleapis.com
Origin: http://localhost:8000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:48.0) Gecko/20100101 Firefox/48.0
The header of the OPTION request from Chrome:
Accept:*/*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:zh-TW,zh;q=0.8,en;q=0.6,en-US;q=0.4,zh-CN;q=0.2
Access-Control-Request-Headers:
Access-Control-Request-Method:PUT
Connection:keep-alive
Host:directupload.storage.googleapis.com
Origin:http://localhost:8000
Referer:http://localhost:8000/
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
X-Client-Data:CIe2yQEIprbJAQjznMoB
The header issue is not coming from your app, I think it's coming from the cloud storage bucket. I had the same issue when setting up an api, the resource you are posting to is missing the header.
https://cloud.google.com/storage/docs/cross-origin
While useful for preventing malicious behavior, this security measure also prevents useful and legitimate interactions between known origins. For example, a script on a page hosted from Google App Engine at example.appspot.com might want to use static resources stored in a Cloud Storage bucket at example.storage.googleapis.com. However, because these are two different origins from the perspective of the browser, the browser won't allow a script from example.appspot.com to fetch resources from example.storage.googleapis.com using XMLHttpRequest because the resource being fetched is from a different origin.
So it looks like you need to configure the bucket to allow cors requests. The google documentation shows the following code to be run from the google cli.
https://cloud.google.com/storage/docs/cross-origin#Configuring-CORS-on-a-Bucket
gsutil cors set cors-json-file.json gs://example
[
{
"origin": ["http://mysite.heroku.com"],
"responseHeader": ["Content-Type"],
"method": ["GET", "HEAD", "DELETE", "PUT"],
"maxAgeSeconds": 3600
}
]
Which would allow you get, upload, and delete content. Hope that helps.
Based on the information in EDIT 2, something is wrong with the request. The preflight (OPTIONS) request includes the header ACCESS-CONTROL-REQUEST-HEADER. This is not a valid CORS header. The correct header is ACCESS-CONTROL-REQUEST-HEADERS, notice the 'S' at the end.
Even if the header was correct, it should not be requesting authorization for a access-control-allow-origin header. ACCESS-CONTROL-ALLOW-ORIGIN is not a header that is sent from the client. It is a header that will automatically be sent in the response from the server to the client when the server gets a preflight request. The client/browser will not allow a cross-origin PUT request unless it gets a ACCESS-CONTROL-ALLOW-ORIGIN header authorizing the browser document's current origin from the cross-origin server in the preflight request.
The presence of the bad header appears to correlate well with the error response you are receiving. However, it looks like that header was probably not in your original code, it looks like you added it later (based on your comments). Make sure to take that header config out, it is definitely not correct.
So I am a little confused about where that header is coming from, but I think it is the source of your problem.
It looks like you are using jQuery to make the AJAX PUT request. All I can really suggest is to make sure you haven't called $.ajaxSetup() somewhere in your JS code that might be configuring the bad header.
After so many trials and errors, I came up with the following. The programs worked, however, sometimes/some of the uploaded images are not visible; other times they are OK. I have no idea why this happened.
I'd like to solicit more ideas why file uploads are OK but some of the images are corrupted.
gsutil commands:
gsutil cors set cors.json gs://<bucketName>
gsutil defacl ch -u allUsers:R gs://<bucketName>
Content of cors.json file:
[
{
"origin": ["*"],
"responseHeader": ["Content-Type"],
"method": ["GET", "POST", "HEAD", "DELETE", "PUT"],
"maxAgeSeconds": 3600
}
]
HTML:
<p id=status>Choose your avatar:</p>
<input id=fileInput type=file>
JavaScript:
$(document).on('change', '#fileInput', function() {
var $this = $(this);
var file = $this[0].files[0];
$.ajax({
url: 'upload/sign/?fileName=' + file.name + '&contentType=' + file.type,
type: 'GET'
})
.done(function(data) {
var response = JSON.parse(data);
uploadFile(file, response.presignedUrl, response.url, response.contentType)
})
.fail(function() {
alert('Unable to obtain a signed URL.');
});
});
function uploadFile(file, presignedUrl, url, contentType) {
var postData = new FormData();
postData.append('file', file);
$.ajax({
url: presignedUrl,
type: 'PUT',
data: postData,
headers: {
'Content-Type': contentType,
},
processData: false,
contentType: false
})
.done(function() {
alert('File upload successful');
})
.fail(function() {
alert('Unable to upload the file.');
});
}
Django:
Project's urls.py:
urlpatterns = [
...
url(r'upload/', include('upload.urls', namespace='upload')),
]
App's urls.py:
urlpatterns = [
url(r'^$', views.upload, name='upload'),
url(r'^sign/', views.sign, name='sign'),
]
views.py:
def upload(request):
# ... render the template
def sign(request):
fileName = request.GET.get('fileName')
contentType = request.GET.get('contentType')
conn = boto.connect_gs(gs_access_key_id=GS_ACCESS_KEY,
gs_secret_access_key=GS_SECRET_KEY)
presignedUrl = conn.generate_url(3600, 'PUT', GS_BUCKET_NAME, fileName, headers={'Content-Type':contentType})
return HttpResponse(
json.dumps({
'presignedUrl': presignedUrl,
'url': GS_URL + fileName,
'contentType': contentType
})
)
I’m my experience I would like to note “It is not possible to by-pass heroku 30s timeout without using javascript AWS SDK”. Don’t use python AWS SDK (boto). You have to completely leave the back-end out of this. Now from your access origin error, the solution is your CORS. You should put this on your CORS Policy:
[
{
"AllowedHeaders": [
""
],
"AllowedMethods": [
"GET",
"PUT",
"POST",
"DELETE",
"HEAD"
],
"AllowedOrigins": [
""
],
"ExposeHeaders": [
"ETag"
]
}
]
Next for javascript AWS SDK. Follow my answer here: Upload file to s3 in front-end with JavaScript AWS SDK on django
There’s a lot missing from the answer I linked as I had to come up with a custom solution because Javascript AWS SDK also passes heroku 30s timeout. What I did was upload the video via javascript SDK than pass the videos ‘AWS url’ another view in a two step django form. With the changing of djnago views I reset heroku 30s timeout with the video already in my s3 bucket, and passed the fileKey to my url with the redirect. On the second part of the form I gain other information for my djnago object than submit it. This was so hard going through all documentation of direct upload to s3. If anyone is reading this and need help please comment for more. I’m on my phone now but I’ll kindly respond from my desktop to post code snippets ✌🏾