Fetch not receiving headers JS from Flask send_file() - javascript

I'm writing a full stack app. I have a python backend using flask that sends a file and a Vue client that receives. Its been working fine up until the point when I try to send the filename over using a Content-Disposition header.
On the backend I've tried:
return send_file(base_path + filename, as_attachment=True, download_name=filename)
And to set the headers manually,
response = make_response(send_file(base_path + filename))
response.headers['Content-Disposition'] = f"attachment; filename=\"{filename}\""
return response
I've also tried to put in headers that would not be blocked by CORS just to see if the request would receive the header but to no avail,
response = make_response(send_file(base_path + filename))
response.headers['Content-Type'] = "sample/info"
return response
I'm printing the header to the console by doing
fetch('http://localhost:4999/rdownload/' + this.$route.params.id, {
method: 'GET'
}).then(res =\> {
if (res.status == '500') { }
console.log(res.headers)
//const header = res.headers.get('Content-Disposition');
//console.log(header)
res.blob().then((blob) => {
/* ... */
})
})
Any help would be appreciated! Thanks :)

Research
In the interest of logging the solution I found and helping out anyone in the future who may be interested in knowing the answer here's what I discovered:
There is a restriction to access response headers when you are using Fetch API over CORS.
https://stackoverflow.com/a/44816592/20342081
So, no matter what using the JS fetch-api you will be unable to access all headers (outside of Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, and Pragma) unless you expose them by specifying them in a request header. That would look something like this:
fetch('https://myrequest/requestend/', {
headers: {
'Access-Control-Expose-Headers': 'Content-Disposition'
}
})
When a cross-origin source accesses your API you will have to expose the header from the back end as well. https://stackoverflow.com/a/66291644/20342081
I was also confused about how the differences between Access-Control-Expose-Headers and Access-Control-Allow-Headers. In my case the solution was use "expose headers" on both the frontend and the backend (and allow wouldn't work). However, Allow has its own applications which I have yet to understand fully. For those endeavoring check out: https://stackoverflow.com/a/28108431/20342081
Solution
I implemented these things in my code by doing:
class RequestResult(Resource):
def get(self, index):
base_path = f"Requests/{index}/"
filename = os.listdir(base_path)[0]
response = make_response(send_file(base_path + filename, as_attachment=True, download_name=filename))
response.headers['Access-Control-Expose-Headers'] = "Content-Disposition"
return response
And on the front end exposing the header as well on the fetch request:
fetch('http://localhost:4999/rdownload/' + this.$route.params.id, {
method: 'GET',
mode: 'cors',
headers: {
'Access-Control-Expose-Headers': 'Content-Disposition'
}
})
I hope this is helpful for the next 5 people who open this in the next 10 years!

Related

Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response when using http get req from JS to SlackAPI

I understand that there are many similar questions, but I am posting this because I feel it is slightly different.
I am trying to send a GET request to the Slack API using an HTTP request.
Specifically, the code looks like the following.
import useSWR from "swr";
const useSlackSearch = (query: string) => {
const token = process.env.NEXT_PUBLIC_SLACK_API_USER_TOKEN;
const myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer " + token);
const slackURL = `https://slack.com/api/search.messages?query=${query}`;
const fetcher = async (url: string) => {
const response = await fetch(url, {
headers: myHeaders,
}).then((res) => res.json());
return response;
};
const { data, error } = useSWR(slackURL, fetcher, {
revalidateOnFocus: true,
revalidateOnReconnect: true,
});
if (error) {
return console.log(`Failed to load: ${error}`);
} else if (!data) {
return console.log("Loading...");
} else {
console.log(data);
return data;
}
};
export default useSlackSearch;
The environments I'm using are as follows.
Device: MacBook Air
OS: macOS
Browser: Chrome
From: localhost:3000
To: Slack API html page (https://slack.com/api/search.messages)
After reading the MDN articles like below, I understood that
There is such a thing as a simple HTTP request as defined by MDN
If the request you want to send does not correspond to this simple request, the browser will send a preflight request
In the response to that preflight request, there is a header called Access-Control-Allow-Headers.
Only headers set to the value of this Access-Control-Allow-Headers header can be used as headers in the main request after preflighting.
In this case, I tried to use the Authorization header, but it was trapped by the above restriction.
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
That's all I understand.
However, on the official Slack API page for the method in question, it says to specify the token in the Authorization header, so I'm having trouble.
I also don't understand how to specify the Access-Control-Request-Headers in the preflight header, as described in another questioner's thread. The reason is that the only thing that communicates to the Slack API is the browser in this case, and the only relevant source is JavaScript (React / Next.js to be exact)!
After that, I found preflight response from Slack API as follows;
access-control-allow-headers: slack-route, x-slack-version-ts, x-b3-traceid, x-b3-spanid, x-b3-parentspanid, x-b3-sampled, x-b3-flags
As I thought, I understand that Authorization is not allowed because it is not included as a value. So the question is how to solve it.
Furthermore, I found out later that the preflight request from the browser properly declared that it wanted to use Authorization as an actual request header. However, the preflight response did not contain the value.
Following CBroe's advice, I was able to contact the Slack help center directly, so I asked this problem. What I found out as a result is that HTTP requests from browsers are not supported as of the end of February 2022. Of course, they have received quite a lot of requests regarding this, so they hope to address it at some point.
This time, the browser sent Access-Control-Request-Headers:Authorization in the preflight request. But the Slack API server side did not allow the Authorization header in the request from the browser. Therefore, Authorization was not set in the Access-Control-Allow-Headers in the preflight response from the Slack API side.
As a result, the response from the Slack API side returned Invalid Auth, even though Authorization was added as a header when making an actual request from the browser.
Through this error, I gained a deeper understanding of HTTP requests such as CORS and preflighting, but since it is not explicitly written on the official Slack website, I left it here.
What is Preflight: https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
What is Access-Control-Allow-Header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
What is CORS simple request: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
I could not get the Authorization header to work either. However, Slack provided this example for adding token authentication to the Post body following the deprecation of the query parameters method.
This worked for me to make Web API calls to Slack from the browser (for testing) so that Slack would read the token for authentication. Note, according to Slack's best practices for security, user and bot tokens should be stored with care and not used in client-side Javascript:
try {
const res = await fetch("https://slack.com/api/conversations.list", {
method: "POST",
body: `token=${TOKEN}`, // body data type must match "Content-Type" header
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
}).catch((error) => {
console.log(error);
});
if (!res.ok) {
throw new Error(`Server error ${res.status}`);
} else {
const data = await res.json();
console.log(data);
}
} catch (error) {
console.log(error);
}
using token in request body instead of Authorization header worked for me.
axios({
method: 'post',
url: 'https://slack.com/api/chat.postMessage',
data: `text=Hi&channel=D048GGYTJUK&token=${process.env.TOKEN}`
})

Problems with CORS and JSON

I habe a client written in Javascript (react) which runs on localhost:3000
Here I have a button that sends the credentials to my backend written in python and using flask. The endpoint is running on localhost:5000/login.
My frontend code looks like this:
loginToDatabase = async () => {
console.log("login to database. user: " + this.state.user+" pw: "+this.state.password);
const response = await fetch("http://localhost:5000/login",{
method: 'POST',
body: {
"user": this.state.user,
"password": this.state.password
},
headers: {
"Content-Type": "application/json"
}
});
console.log("response: "+response)
};
My backend code looks like this:
#app.route("/login", methods=['POST'])
def login():
jsonRequest = request.get_json()
receivedUser = jsonRequest.get('user')
receivedPassword = jsonRequest.get('password')
isConnected = opendatabase.openDatabase('localhost',receivedUser,receivedPassword)
if(isConnected == True):
body = json.dumps({
"connection":isConnected,
})
jsonResponse = Response(
body,
mimetype="application/json",
headers={
"Access-Control-Allow-Origin" : "*",
}
)
return (jsonResponse)
else:
body = isConnected
jsonResponse = Response(
body,
headers={
"Access-Control-Allow-Origin" : "*",
)
return (jsonResponse, 401)
Testing the API with Postman works as expected. However using the frontend I receive this error:
Access to fetch at 'http://localhost:5000/login' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
After doing some research I figured out why this happens and that I need the header
"Access-Control-Allow-Origin" : "*"
which I included. But still could not make it work.
I then read here https://developer.mozilla.org/de/docs/Web/HTTP/CORS that does not work with application/json.
The only allowed values for the Content-Type header are:
application/x-www-form-urlencoded,
multipart/form-data,
text/plain
However I also read here Can't send a post request when the 'Content-Type' is set to 'application/json' that I can set a header
->header('Access-Control-Allow-Headers', 'Content-Type');
which I also tried like this:
if(isConnected == True):
body = json.dumps({
"connection":isConnected,
})
jsonResponse = Response(
body,
mimetype="application/json",
headers={
"Access-Control-Allow-Origin" : "*",
"Access-Control-Allow-Headers": "Content-Type",
}
)
return (jsonResponse)
else:
body = isConnected
jsonResponse = Response(
body,
headers={
"Access-Control-Allow-Origin" : "*",
"Access-Control-Allow-Headers": "Content-Type"
}
)
return (jsonResponse, 401)
But that does also not work.
There are also many more posts about this topic but so far I could not find a solution. I am also new to APIs and webdevelopment.
And also I think my shown login process is far from perfect. It is really just about getting the REST call to work.
Is there a way to solve this and still using JSON? Surely I could use someting like text/plain but that would not be satisfying.
Also (maybe related) if I use text/plain in the frontend I don't receive the error, but in the backend I don't know how the receive the data send via POST. As you can see in the attached picture, I seem to get an empty object?
Where is my mistake and what is the best way to solve this issue with JSON?

How to fetch data from different origin CORS? [duplicate]

This question already has answers here:
No 'Access-Control-Allow-Origin' header is present on the requested resource—when trying to get data from a REST API
(26 answers)
Closed 3 years ago.
I'm trying to fetch data from a different origin to another server using Fetch API and I precise is from http to https
I can read the data from my browser but I don't know how to fetch them.
I already tried to set Access-Control-Allow-Origin to * but I still get this message :
I'm a little bit lost right know, Thank you for your support. 😁
const myHeaders = new Headers({
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json"
});
const fetchConfig = {
method: "GET",
headers: myHeaders,
mode: "cors",
cache: "no-cache"
};
function fetchData(url) {
fetch(url, fetchConfig)
.then(response => {
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => console.error(error));
}
fetchData("https://api.example.com/");
The Access-Control-Allow-Origin header needs to be set by the server you are retrieving the data from, in response to your request.
CORS Anywhere is a NodeJS proxy which adds CORS headers to the proxied request.
The URL to the proxy is literally taken from the path, validated and proxied. The protocol part of the proxied URI is optional, and defaults to "http". If port 443 is specified, the protocol defaults to "https".
This package does not put any restrictions on the http methods or headers, except for cookies. Requesting user credentials is disallowed. The app can be configured to require a header for proxying a request, for example, to avoid a direct visit from the browser.
You can simply add https://cors-anywhere.herokuapp.com/ at the beginning of your url.
Like this https://cors-anywhere.herokuapp.com/http://example.com/api/....
Check this link for more details: https://www.npmjs.com/package/cors-anywhere

Adding headers to fetch, solving cors, running cryptojs client side

I'm working on a project where I need first to get a authentication token from a server.
For this I need to send a GET request to the authorisation server with two key-value pairs included in the header: 1/ the client id (const) and 2/ a HMAC SHA1 calculated value based on client ID timestamp and so on.
This is working fine with Postman. (I calculate the sha1 on an online calculator)
Problem 1: (cryptojs client side)
As a node app I included the cryptojs library and the calculation works. But even with RequireJS I can not get cryptojs to run in the browser.
Error: Module name "crypto-js" has not been loaded yet for context: _. Use require([])
Problem 2: (cors)
Apparently chrome refuses the connection as the server does not accept all incoming connections.
Adding mode: 'no-cors' to the fetch request does not solve the problem.
Problem 3: (headers)
I need to add two key - value pairs to the get request headers. In postman this is no problem but I'm not sure this works with append or just adding them to my headers: { }
I constantly get a server error as if no headers where added.
I have already tried REquireJS for the cryptojs problem.
I have added the headers to a myHeaders object
const myHeaders = new Headers();
myHeaders.append('ClientID', CLIENTID);
myHeaders.append('Clientsecret', hashedToken);
and also just added the values to:
headers: {
...
'ClientID': CLIENTID,
'Clientsecret': hashedToken,
}
Both don't seem to help.
My code:
function getToken(){
hashedToken = getHashedSecret(); //this won't work client side as cryptojs can not be loaded
const CLIENTID = "CLIENTID";
const AUTHURL = "https://authorization.server.com/api/CLIENTID/authorization/";
var TIMESTAMP = getTimeStamp();
const myHeaders = new Headers();
// myHeaders.append('Content-Type', 'application/json');
myHeaders.append('ClientID', CLIENTID);
myHeaders.append('Clientsecret', hashedToken);
console.log(myHeaders);
let response = fetch(AUTHURL+TIMESTAMP, {
method: 'GET',
headers: {
myHeaders,
'Accept': 'application/json',
'Content-Type': 'application/json',
'Origin': '',
'Host': 'authorization.server.com',
include: 'ClientID', CLIENTID
},
mode: 'no-cors',
})
.then(response => response.json())
.then(data => {
console.log(data);
document.getElementById('output').innerHTML = data;
})
.catch(error => console.error(error))
console.log('data');
return data;
}
I should get a token from the server
It sounds like https://authorization.server.com doesn't allow access from your page's origin. Remember that in browsers, the Same Origin Policy prevents scripts from one origin from requesting information from other origins by default. (postman, not being a browser, is not subject to this restriction). This is so that scripts on Site A (a malicious actor) can't steal your personal information from Site B (perhaps your online banking) by making requests (from your browser, thus with your authentication information) to Site B.
For this to work, server code at https://authorization.server.com will need to respond to requests from the browser using the Cross-Origin Resource Sharing to allow access from your origin. You cannot do it from your client-side code (for obvious reasons).
Alternately, you can run a server on your origin, make the requests to that server, and it can make the requests to https://authorization.server.com and pass back the responses to you. Your server, not being a browser, is not subject to the SOP.

Uncaught (in promise) TypeError: Failed to fetch and Cors error

having a problem with getting data back from database. I am trying my best to explain the problem.
1.If I leave "mode":"no-cors" inside the code below, then I can get data back from server with Postman, but not with from my own server. Thinking it has to be my client side error
When I remove "mode":"no-cors" then I am getting 2 errors:
-Fetch API cannot load http://localhost:3000/. Request header field access-control-allow-origin is not allowed by Access-Control-Allow-Headers in preflight response.
-Uncaught (in promise) TypeError: Failed to fetch
Quick Browsing suggested to put in the "mode":"no-cors" which fixed this error, but it does not feel right thing to do.
So I thought maybe somebody has a suggestion how to approach this problem.
Really hope I was clear enough, but pretty sure I am not giving clear explanation here :S
function send(){
var myVar = {"id" : 1};
console.log("tuleb siia", document.getElementById('saada').value);
fetch("http://localhost:3000", {
method: "POST",
headers: {
"Access-Control-Allow-Origin": "*",
"Content-Type": "text/plain"
},//"mode" : "no-cors",
body: JSON.stringify(myVar)
//body: {"id" : document.getElementById('saada').value}
}).then(function(muutuja){
document.getElementById('väljund').innerHTML = JSON.stringify(muutuja);
});
}
Adding mode:'no-cors' to the request header guarantees that no response will be available in the response
Adding a "non standard" header, line 'access-control-allow-origin' will trigger a OPTIONS preflight request, which your server must handle correctly in order for the POST request to even be sent
You're also doing fetch wrong ... fetch returns a "promise" for a Response object which has promise creators for json, text, etc. depending on the content type...
In short, if your server side handles CORS correctly (which from your comment suggests it does) the following should work
function send(){
var myVar = {"id" : 1};
console.log("tuleb siia", document.getElementById('saada').value);
fetch("http://localhost:3000", {
method: "POST",
headers: {
"Content-Type": "text/plain"
},
body: JSON.stringify(myVar)
}).then(function(response) {
return response.json();
}).then(function(muutuja){
document.getElementById('väljund').innerHTML = JSON.stringify(muutuja);
});
}
however, since your code isn't really interested in JSON (it stringifies the object after all) - it's simpler to do
function send(){
var myVar = {"id" : 1};
console.log("tuleb siia", document.getElementById('saada').value);
fetch("http://localhost:3000", {
method: "POST",
headers: {
"Content-Type": "text/plain"
},
body: JSON.stringify(myVar)
}).then(function(response) {
return response.text();
}).then(function(muutuja){
document.getElementById('väljund').innerHTML = muutuja;
});
}
In my case, the problem was the protocol. I was trying to call a script url with http instead of https.
try this
await fetch(url, {
mode: 'no-cors'
})
See mozilla.org's write-up on how CORS works.
You'll need your server to send back the proper response headers, something like:
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, PUT, GET, OPTIONS
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization
Bear in mind you can use "*" for Access-Control-Allow-Origin that will only work if you're trying to pass Authentication data. In that case, you need to explicitly list the origin domains you want to allow. To allow multiple domains, see this post
you can use solutions without adding "Access-Control-Allow-Origin": "*", if your server is already using Proxy gateway this issue will not happen because the front and backend will be route in the same IP and port in client side but for development, you need one of this three solution if you don't need extra code
1- simulate the real environment by using a proxy server and configure the front and backend in the same port
2- if you using Chrome you can use the extension called Allow-Control-Allow-Origin: * it will help you to avoid this problem
3- you can use the code but some browsers versions may not support that so try to use one of the previous solutions
the best solution is using a proxy like ngnix its easy to configure and it will simulate the real situation of the production deployment
Sometimes, please check your port number. If localhost port number is mismatch, you will get the same error as well.
I was getting this error and realized my server.js wasn't running.

Categories