Separate different failures when using fetch - javascript

I am using fetch to make requests to the backend of my application, which uses an OAuth authentication provider.
When the session expires, the fetch request is redirected to the authentication provider, causing it to fail because of a CORS error (which I cannot change).
In this case, fetch throws a TypeError: failed to fetch.
Unfortunately, I am also getting a TypeError: failed to fetch, when the network is down or the request times out for another reason.
How can I reliably discriminate between these failure modes?
I have not found any useful properties on the thrown TypeError, that would allow these failures to be distinguished.

It is possible to catch redirects using the redirect: "manual" option.
fetch(url, {redirect: "manual"})
While it is not possible to catch the redirection URL for security purposes, this is an acceptable resolution for me at this point.

Related

Firefox is blocking fetch requests on same origin

I'm a little stumped with this one, I'm making a request in firefox to a relative url:
await fetch('/foo/bar',{});
This errors out with:
TypeError: NetworkError when attempting to fetch resource.
Furthermore, the urls show up as 'blocked' in the network tab:
Things I've checked:
uBlock origin is off
Tracking protection is off
Server is running, the request succeeds when I directly open it in my browser, or via CURL.
This is not related to CORS, as the origins are all local.
I figured out the issue.
The fetch() request was initiated.
An unrelated error was thrown.
This error was not checked correctly, causing the browser to redirect to a different url.
This redirect caused the (still running) fetch request to abort.
The aborted request shows up as a block, setting me on an incorrect path to try and fix this.
So things were broken, just not where I expected. Thank you for helping me think and find the root cause

Catch Javascript fetch failing with Cloudflare 429 missing CORS header

I am using Fetch to make cross origin requests in javascript.
Cloudflare (proxying my traffic) will sometimes return a 429 (rate limiting).
When they return 429, they do not include the Access-Control-Allow-Origin header.
So now my fetch with mode: 'cors' fails, and throws a TypeError
How can I catch when this happens, vs. when it throws for other reasons like network errors?
My code is as follows:
try {
let response = await fetch(uri, config); // this throws
if (!response.ok) { // this line does not run
throw response.statusText;
}
let json = await response.json();
return json;
} catch (e) {
console.log(e.message); // "Failed to fetch"
}
Checking MDN docs, I'm not sure if it's possible to detect this 429 separate from other network errors?
A fetch() promise will reject with a TypeError when a network error is encountered or CORS is misconfigured on the server-side, although this usually means permission issues or similar
The short version is you can't, and this is by design.
The only place where this could be fixed is the server-side. Either Cloudflare needs to be changed/configured to send the appropriate headers or using a different service that does send the headers on error.
Without a server-side change, the error will be a generic CORS error.
The other alternative might to do build something like an 'iframe proxy', effectively letting you circumvent CORS entirely.
status code: 429 is returned when user sends too many requests in given time.
that means eventListners are making multiple requests(thats what I think).
So, to stop eventListners from making multiple requests use
event.stopImmediatePropagation();
If several listeners are attached to the same element for the same event type, they are called in the order in which they were added. If stopImmediatePropagation() is invoked during one such call, no remaining listeners will be called.
read more about this here MDN docs.

CORS error, but data is fetched regardless

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.

accessing external site gives "mixed content" error with angular $resource

my server is running over https, but I need to be able to access a resource on an external site (that I have no control over) that is only available via http
I've used the user $resource setup
var tableDefintion = $resource('http://www.externalsite.org/xx/info.php',
{
param: '#data'
}
but, obviously, when I make a resource request I get the dreaded "Mixed Content" message and it will not load
I have tried adding $sceDelegateProvider.resourceUrlWhitelist to the angular config function, but that has made no difference, even when set to ['**']
This resource is being used from within a service - is there anything else I can do to get round this issue ?
thanks
If you are trying to do this by getting the client (browser) to do this request then there is no way round this (MDN) as it's a security risk. However if you have a server why not proxy the request via your server so you can do the TLS termination yourself?

Gracefully Handling 401 Not Authorized with Angularjs $http

I am currently working on an problem with a login page for an AngularJS app. The login page uses the $http service to submit the username and password using Basic authentication (Authorization: Basic (username and password in base 64)) to a Web service that may or may not be running on the same server that the webapp is hosted on. If authentication succeeds, the server returns a response with status 200 OK; otherwise, it returns a 401 Not Authorized. We have an error function to gracefully handle this error condition, but before it is even called, the browser by default shows a modal dialog for the user to enter his or her username and password. How can I prevent this?
I've looked at several sources, including the W3 standards for XHR.send(), for help, and my findings show that in Firefox and Chrome, the browser dialog pops up after a 401 response to an XHR call when all of the following are true:
1) The request is "same-origin" (which it is in the cases where the web service is running on the same server that serves the webapp)
2) The response includes the header WWW-Authenticate: Basic [some realm] or WWW-Authenticate: Digest [some realm] (our web service returns the former)
3) The request was not made by specifically setting a username and password in the XHR object whose send() method was called.
I found a test site (which SO won't let me link to) that exhibits the desired behavior for 401 responses (no modal login box). It meets the first two conditions but avoids meeting the third, apparently because it sets the XHR object's username and password by sending them as arguments #4 and 5 to its open() function. I'm not sure if there's any other way to set these properties of the XHR object, since I can't even find them by inspecting the XHR object in a browser's JavaScript debugger.
The problem is that our site's login service doesn't itself call XmlHttpRequest.open() to pass the username and password; instead, it creates the Authorization header itself and passes it in the headers collection of a params object to $http. By inspecting the source of angular.js, I found that $http then proceeds to call open() with only three parameters.
Does Angular provide a way to have $http call the 5-parameter overload of open(), or otherwise prevent this login dialog from showing up? If not, I can only think of a few workarounds:
1) Somehow decorate $http to enforce calling the 5-parameter overload of open() if a username and password are provided
2) Somehow use $httpBackend to achieve the same as #1 (though the documentation discourages developers from using $httpBackend so I'm not sure if this is a good idea or even possible)
3) Ignore $http and have my login service create and send the XHR itself
4) Alter the server so it doesn't return the WWW-Authenticate: Basic header (least desirable)
Is there a well-known or "right" way to work around this problem?
The issues you are facing is due to the first reason you have specified
1) The request is "same-origin" (which it is in the cases where the web service is running on the same server that serves the webapp).
solution
Either add access-control-allow-origin header to your backend service in order to acccept calls from different origin.
or
if you are in development phase and later your frontend and service are anyway going to be in same origin,then use https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi?hl=en extension ,
this will allow you to override chrome cross origin policy (this is just for development,its a workaround).
or
Open chrome with web security disabled by opening chrome from command prompt
chrome.exe --user-data-dir="C:/Chrome dev session" --disable-web-security

Categories

Resources