kit icon indicating copy to clipboard operation
kit copied to clipboard

fetch() unexpected behavor when encountering a CORS issue on client side

Open miohtama opened this issue 2 years ago • 3 comments

Describe the bug

If SvelteKit's fetch() is called against a URL that causes a CORS error in a web browser, the resulting behaviour might be unexpected for a developer. The behaviour is also naturally different on client-side and server-side rendering, where SSR fetch() does not suffer from CORS issues.

  • A CORS error does not return a response in fetch() but throws an exception. Developers relying on response.ok pattern to check the validity of the fetch result are caught off guard.
  • This is a tough nut to crack, because it is rooted deeply in the web browser security model.
  • The behaviour is unexpected because you need to work around using try ... catch instead of response.ok attribute to check the error.
  • Although something must "have gone wrong before" this occurs, the unexpected behaviour is common, because many times HTTP 404 or HTTP 500 responses lack CORS headers on them. If a developer naively checks for response.status === 404 the check might fail in a web browser, assuming a low quality third party API. This can happen if a developer is using fetch() in load(), get() or similar with third-party APIs. This is especially bad, when SSR renders different pages (w/working error handling) than the client-side router.

Reproduction

This issue cannot be demostrated on StackBlitz (node.new) because it is related to low-level network primitives.

Here is an example one can throw in their load() function:


            try {
                // causes CORS error because lack of CORS headers in 404
                resp = await fetch("https://example.com/random");
            } catch(e) {
                // A web browser throws TypeError: Failed to fetch
                // but happens only on client-side.
                // The exception is hard to distinguish from
                // any other exception, because it lacks metadata
                // (class name, attributes).
                console.error("Received", e)
                // Temporary workaround
                resp = {ok: false, statusText: e.message};
            }

	let error, meta;
	if (resp.ok) {
		meta = await resp.json();
		error = null;
	} else {
		meta = {};
		error = resp.statusText;
	}

Logs

You will see this in the web developer console:

Access to fetch at 'https://example.com/random' from origin 'http://localhost:3000' has been blocked by CORS policy: 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.

However I am not sure if this information is accessible within client-side JavaScript.

System Info

System:
    OS: macOS 12.3.1
    CPU: (10) arm64 Apple M1 Max
    Memory: 902.50 MB / 64.00 GB
    Shell: 5.8.1 - /opt/homebrew/bin/zsh
  Binaries:
    Node: 16.14.2 - ~/.nvm/versions/node/v16.14.2/bin/node
    Yarn: 1.22.15 - ~/.nvm/versions/node/v16.14.2/bin/yarn
    npm: 8.7.0 - ~/.nvm/versions/node/v16.14.2/bin/npm
  Browsers:
    Chrome: 101.0.4951.64
    Firefox: 100.0.2
    Safari: 15.4
  npmPackages:
    @sveltejs/adapter-auto: next => 1.0.0-next.34 
    @sveltejs/kit: next => 1.0.0-next.308 
    svelte: ^3.46.0 => 3.46.6

Severity

annoyance

Additional Information

I am not sure what would be the best way to address the issue.

If this issue is not fixable on a framework level, I suggest this issue is highlighted in

  • tutorial
  • examples
  • documentation
  • etc.

and developers are educated that fetch() may throw an exception, besides returning error responses.

There are likely to be other similar low-level network issues in a web browser that needs to be handled via catch in a fetch() caller.

miohtama avatar May 25 '22 18:05 miohtama

This would be a documentation thing, if anything. There's nothing SvelteKit can do about CORS. If the fetch is throwing an exception and JS in the site is unable to see what the error response is to its request, SvelteKit's not going to be able to see it either. I don't think SvelteKit should try to catch this error on its own and either try to replace it with a different error or replace it with a fake Response with !resp.ok.

Conduitry avatar May 25 '22 18:05 Conduitry

I am happy to tackle this. Is there anything special needed for doc updates - like discussing with someone beforehand?

miohtama avatar Jun 02 '22 11:06 miohtama

Could SvelteKit's fetch wrapper not intercept the response and see if it would throw if it happened in a browser? Presumably if we make a request to a different origin, we just need to check the Access-Control-Allow-Origin header in the returned response a) exists and b) is either equal to * or event.url.origin.

If mode === 'no-cors' we'd also need to proxy the response object so that we can replace the body with an empty string, but that also feels doable.

Rich-Harris avatar Jul 15 '22 22:07 Rich-Harris

We seem to get this new simulated CORS error a lot on netlify deploys. Not sure if there's some caching going on or if the render-function sets the origin to different values. I'll try to figure out what's going on, but if somebody has a pointer I'm all ears ;)

bummzack avatar Sep 06 '22 15:09 bummzack

When testing sveltekit on localhost with an API on example.com this will now throw an error. Is there a way stop simulating the error for localhost?

ivands avatar Sep 13 '22 21:09 ivands

Same problem here. I can get the data I want in a local console, and it will render to the DOM for a split second until I get hit with the same error previously described.

engageintellect avatar Sep 14 '22 02:09 engageintellect

Same here - started getting this error recently

smblee avatar Sep 14 '22 19:09 smblee

Same problem here. I can get the data I want in a local console, and it will render to the DOM for a split second until I get hit with the same error previously described.

UPDATE: passing "&origin=*" into my API call fixed my issue. Weird as this was working without it prior to sveltekit update.

engageintellect avatar Sep 14 '22 20:09 engageintellect

Same problem here. When using an AWS AppSync won't return a CORS header from a SSR fetch call, it does work in the browser. I want to opt-out this check.

MicMonen avatar Sep 15 '22 11:09 MicMonen

There was honestly not much value in having SvelteKit "stimulate" CORS, now people need to worry about CORS issues in both the browser AND SvelteKit, which is often not straightforward.

aradalvand avatar Nov 27 '23 06:11 aradalvand