I have a react application using ES6 fetch API to call a rest endpoint using CORS. The fetch call work just fine, but I'm unable to get the response headers that are being sent from the server. I can see the response headers in Chromes devtools so I know they are being sent back to me; however access to them in code doesn't seem to work for me. Here is the code (its basic fetch promise-chaining):
fetch(url)
.then(response => {
for (let header of response.headers) { < -- - headers is empty
console.log(header);
}
return response;
});
But response.headers is empty. But I can clearly see the headers in Chrome. Is there a reason why the fetch call is blocking me from viewing them? What do I need to get access to these headers? The server stores some application specific headers that we would love to use.
UPDATE: I was able to fix this by having the server add this to the response of the OPTION request:
response.setHeader("Access-Control-Expose-Headers", "X-Custom-Header");
Now on the response of the fetch command, I can do the following:
const customHeader = response.headers.get('X-Custom-Header');
Thank you Karim for the help on this!
To get a specific header you need to call response.headers.get(key)
and the iterable object is response.headers.entries and not response.headers
for (let header of response.headers.entries()) {
console.log(header);
}
https://developer.mozilla.org/en-US/docs/Web/API/Headers/entries
In addition, in a cors scenario only some headers are exposed to the app, thus not all the headers you see on the chrome dev tools are necessarily available at application level.
For further details check this answer:
Reading response headers with Fetch API
Related
I'm trying to work with the eBay APIs. It's a small personal project that just needs to run locally, and although I know C#, I'm much more comfortable with Javascript so I'm looking for ways to get this done in JS.
I found this promising looking eBay Node API with browser support. Browser support is something I'm looking for, but it also says that
A Proxy server is required to use the API in the Browser.
They give an example file for a proxy server that is a Cloudflare worker.
I'm trying to translate that into something I can run in Node locally using the basic Node HTTP server. I've been following through it and am doing OK so far, figured out the different ways to access the headers and check them, etc., but now I'm at the point where the proxy server is making the proxy request to the eBay APIs. The way the example file is set up, it seems as though the Cloudflare worker intercepts the HTTP request and by default treats it as a Fetch Request. So then when it goes to pass on the request, it just kind of clones it (but is replacing the headers with "cleaned" headers):
// "recHeaders" is an object that is _most_ of the original
// request headers, with a few cleaned out, and "fetchUrl"
// is the true intended URL to query at eBay
const newReq = new Request(event.request, {
"headers": recHeaders
});
const response = await fetch(encodeURI(fetchUrl), newReq);
The problem is that I don't have a Fetch Request to clone - since I'm running a Node HTTP server, what is event.request in the example code is for me a http.IncomingMessage.
So how can I turn that into a Fetch Request? I'm guessing at the very least there's stuff in the message body that needs to get passed along, if not other properties I'm not even aware of...
I don't mind doing the work, i.e. reading the stream to pull out the body, and then putting that into the Request object somehow (or do I even need to do that? Can I just pipe the stream from the IncomingMessage directly into a Request somehow?), but what else besides the body do I need to make sure I get from the IncomingMessage to put into the Request?
How do I turn a Node http.IncomingMessage into a Fetch Request and be sure to include all relevant parts?
I've made a simple function to convert.
const convertIncomingMessageToRequest = (req: ExpressRequest): Request => {
var headers = new Headers();
for (var key in req.headers) {
if (req.headers[key]) headers.append(key, req.headers[key] as string);
}
let request = new Request(req.url, {
method: req.method,
body: req.method === 'POST' ? req.body : null,
headers,
})
return request
}
Basically, I'm trying to get a username by id from Sequelize. The problem is that I am either stuck with a CORS problem or 500 Internal Server error depending on the response(status)
cors and 500
controller code
async getUserFromUserId (req, res) {
try {
// const user = await User.findByPk(req.body.id)
const id = req.body.id
const user = await User.findByPk(id)
res.send(user.username)
} catch (err) {
// or res.status(some random number).send() for CORS problem to appear
res.status(500).send({
error: 'an error has occured trying to fetch the users id'
})
}
},
client code
this.notifiedUser = (await UserService.getUserFromUserId({id: UserId})).data
I get a Status: 200 OK from postman though.
Postman Solution
Edit:
I have seen how the other Solution for the cors thingy, but the solutions does not specify as to why I get "undefined" results after resolving the cors problem.
So, CORS is actually really obnoxious in this regard, but there's a fairly straightforward way to fix this. It's a super useful security feature, though it is frustrating at best sometimes.
Your browser does what is called a Preflight Request, which is of the http verb OPTIONS. Your browser calls whatever route you want, but instead of what you asked it to do, it calls using OPTIONS first. Your server should accept all routes that the client can ask for with the OPTIONS method, and your server should respond with the following headers to be an externally available, cross-origin API.
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, ...
(note, you should not put the ... in, but you can put any HTTP verb in this list)
If you require your own headers (for auth purposes), you want to add this header for Client -> Server.
Access-Control-Allow-Headers: YourHeader, YourHeader2, YourHeader3
You want to add this one for Server -> Client
Access-Control-Expose-Headers: YourHeader,YourHeader3
Note that the OPTIONS call is an entirely separate call that you should handle as well as the GET method.
You've now told the browser what it is allowed to ask for, and what it can expect to get back from your API. If you don't respond to the OPTIONS request, the browser terminates the request, resulting in a CORS error.
I'm going to take a gander at a guess and assume you're likely using Express, which this answer describes how to set the headers on.
What do the headers mean, in English?
Access-Control-Allow-Origin
From where are clients allowed to access this resource (endpoint)? This can match partial domains with wildcards, or just a * to allow anywhere.
Access-Control-Allow-Methods
What HTTP methods are permissible on this route?
Access-Control-Expose-Headers
When I get a response from the server, what should I (the browser) expose to the client-side?
Access-Control-Allow-Headers
What am I as the client side allowed to send as headers?
Okay, so I figured out the problem.
In a way, I did not have to deal with any of the cors stuff because I believe that was not the main source of the problem.
So, instead of accessing my database data through "GET" and getting the data by doing this:
this.data = (Service.function(bodyValue)).data
I did "POST" to get the data, and accessed the data by simply doing this
const response = Service.function({
id: bodyValue
})
this.data = response.data
This accesses the data without having to get "secured" information from the database, but by accessing the data from the database by getting Observer object info from the database.
The Observer object looks as follows, which treats the user data as an object instead of pure data.
Compared to a data object, where each data {...} has user information.
I am not sure if I am using the correct words, but these are to the extent of my current understanding.
If your origin is from localhost, Chrome usually blocks any CORS request originating from this origin.
You can install this extension:
https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf?hl=en
Or you can disable the security when running chrome (add the flag):
--disable-web-security
I have a question about cryptomarket Binance.
They have public api which I though I could use in angular to create trading app.
But I have some troubles.
Using that link in chrome I get json result.
https://api.binance.com/api/v1/exchangeInfo
But using with angular 4 httpClient:
this.http.get('https://api.binance.com/api/v1/exchangeInfo').subscribe(res => console.log(res));
I have error: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at api.binance.com/api/v1/exchangeInfo. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)
It doesn't work. I don't get it, why I can't use that API in angular app?https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md
What should I do?
Should I set headers like that:
getMarkets() {
const headers = new HttpHeaders();
headers.set('Content-Type', 'application/json');
headers.set('Accept', 'application/json');
headers.set('Access-Control-Allow-Headers', 'Content-Type');
headers.set('Access-Control-Allow-Origin', '*');
const path = 'https://api.binance.com/api/v1/exchangeInfo';
return this.http.get(path, {headers: headers});
}
Thanks in advance
You can't quite use it directly like that, Binance API does not set CORS headers, so Chrome and all major browsers will block the requests.
There is a bit more to it, but essentially, the api servers that need to enable support for CORS should set Access-Control-Allow-Origin to be * or a single domain www.example.com, this allows the browsers to prevent malicious code on a site to call and read the response of some data from other site you might be logged on to ( eg: bank info )
You can read more about it here
One possible solution is to have your own server that proxies calls to binance
Another solution if you're testing things out is to use a CORS enabling extension like this one
Update: You can also use the websocket API if that satisfies your data needs docs
Update 2: Here's a good stackoverflow question on cors
Side note: If your bank's API server sets the Access-Control-Allow-Origin to * then change banks :)
Try this simple request without headers.
this.http.get('https://api.binance.com/api/v1/exchangeInfo').subscribe(data => {
this.results = data;
});
}
It work for me
HttpHeaders is immutable. So you must write
const headers = new HttpHeaders().
set('Content-Type', 'application/json').
set('Accept', 'application/json').
set('Access-Control-Allow-Headers', 'Content-Type').
set('Access-Control-Allow-Origin', '*');
or
const headers = new HttpHeaders(
{
Content-Type:'application/json'),
Accept:'application/json'),
Access-Control-Allow-Headers:'Content-Type'),
Access-Control-Allow-Origin:'*')
})
When querying wikidata, I get a Access-Control-Allow-Origin error.
My request is sent using Axios from a Reactjs web app:
let query = "https://www.wikidata.org/w/api.php?action=wbgetentities&format=json&titles=La_Cha%C3%AEne_Info&sites=enwiki|frwiki";
axios.get(query, (response) => {
...
});
Adding origin=* as for the wikipedia api does not work and replacing www by en returns an insecure response.
Is there a way around this?
I have a generated React site I am hosting in an S3 bucket. One of my components attempts to fetch something when loaded:
require('isomorphic-fetch')
...
componentDidMount() {
fetch(`${url}`)
.then(res => {
console.log(res);
this.setState({
users: res
})
})
.catch(e => {
// do nothing
})
}
The url I am fetching is an AWS API Gateway. I have enabled CORS there, via the dropdown, with no changes to the default configuration.
In my console, for both the remote site and locally during development, I see:
"Failed to load url: No 'Access-Control-Allow-Origin' header is present on the requested resource." etc
However, in the Chrome Network tab, I can see the request and the response, with status 200, etc. In the console, my console.log and this.setState are never called, however.
I understand that CORS is a common pain point, and that many questions have touched on CORS. My question: Why does the response show no error in the Network tab, while simultaneously erroring in the console?
The fetch(`${url}`) call returns a promise that resolves with a Response object, and that Response object provides methods that resolve with text, JSON data, or a Blob.
So to get the data you want, you need to do something like this:
componentDidMount() {
fetch(`${url}`)
.then(res => res.text())
.then(text => {
console.log(text);
this.setState({
users: text
})
.catch(e => {
// do nothing
})
}
Failed to load url: No 'Access-Control-Allow-Origin' header is present on the requested resource." etc
That means the browser isn’t allowing your frontend code to access the response from the server, because the response doesn’t include the Access-Control-Allow-Origin header.
So in order for the above code to work, you’ll need to fix the server configuration on that server so that it sends the necessary Access-Control-Allow-Origin response header.
However, in the Chrome Network tab, I can see the request and the response, with status 200, etc. In the console, my console.log and this.setState are never called, however.
That’s expected in the case where the server doesn’t send the Access-Control-Allow-Origin response header. In that case, the browser still gets the response — and that’s why you can see it in the devtools Network tab — but just because the browser gets the response doesn’t mean it will expose the response to your frontend JavaScript code.
The browser will only let your code access the response if it includes the Access-Control-Allow-Origin response header; if the response doesn’t include that header, then the browser blocks your code from accessing it.
My question: Why does the response show no error in the Network tab, while simultaneously erroring in the console?
For the reason outlined above. The browser itself runs into no error in getting the response. But your code hits an error because it’s trying to access an res object that’s not there; the browser hasn’t created that res object, because the browser isn’t exposing the response to your code.
You may be seeing the status 200 for the OPTIONS not the GET. There is a setting for CORS to handle legacy, so it won't confuse your client. I had to do that last time in a React app. Your error is that your CORS isn't configured properly (sorry, obviously). Chrome won't let your client tlak to the backend if it doesn't get the headers properly. Other browsers probably also, probably React also. It may be some kind of HTTP protocol if only one side has CORS enabled. Someone can correct me there. It's a similar security consideration as sending a request to HTTP from HTTPS. Chrome blocks it.
It looks to me like it's your backend. CORS isn't active or it would put that header on, and after that, you would see errors about origin mismatch in the frontend client.
In my experience, it's a 2-3 step combo, make sure OPTIONS don't send confusing signals to your client (look for settings to do with 200). This is a config setting in your backend. Then, make sure the backend is configured to use CORS. You very specifically need to enter the origin hostname and port that the backend is to expect traffic from.
I could probably give better input if I see what languages and/or frameworks you are using besides React.
This is what you would do in Express JS and node for your Backend:
const cors = require('cors')
// note http or https
app.use(cors({
origin: 'http://example.com:1337',
//origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
optionsSuccessStatus: 200
// some legacy browsers (IE11, various SmartTVs) choke on 204
}))
My last React app was detonating without optionsSuccessStatus by throwing success when it was fail.
To give you a little bit of imagery to work with, CORS is simple but finicky. It's a simple matter of alignment. Once your backend is configured to a) use CORS and b) know who to accept traffic from, it's done. Once your frontend is configured to handle this traffic, it's done. It's like aligning a square peg in a round hole until you get the config settings aligned.
Try using Postman to send some GET requests to the Backend. You can observe the headers from there.