sveltejs / kit

web development, streamlined
https://svelte.dev/docs/kit
MIT License
18.73k stars 1.95k forks source link

CORS errors during server rendering #8314

Open tctree333 opened 1 year ago

tctree333 commented 1 year ago

Describe the bug

When fetching an endpoint using CORS from a +page.js file, SvelteKit throws a CORS error when trying to render the page on the server, such as during development mode or prerendering for production. CORS doesn't seem to make sense in this context, since the fetch request is being performed on the server, which has no origin.

This seems related to https://github.com/sveltejs/kit/issues/7441, but that issue involved +page.server.js files. In this issue, we want to perform the fetch on the client AND the server (during SSR/prerendering).

Reproduction

https://stackblitz.com/edit/cors-error-during-server-render-repro?file=src%2Froutes%2Ftest%2F%2Bpage.js

Using the client-side navigation from / to /test fetches data on the client perfectly fine, but doing a browser navigation/reload on the /test page throws a CORS error on the server. Errors are also thrown when trying to run a build.

Logs

No response

System Info

StackBlitz WebContainer

Binaries:
    Node: 16.14.2 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 7.17.0 - /usr/local/bin/npm
  npmPackages:
    @sveltejs/adapter-auto: ^1.0.0 => 1.0.0 
    @sveltejs/kit: ^1.0.0 => 1.0.1 
    svelte: ^3.54.0 => 3.55.0 
    vite: ^4.0.0 => 4.0.3

Severity

serious, but I can work around it

Additional Information

No response

markjaquith commented 1 year ago

I do experience this issue on your Stackblitz reproduction, but when I download the project and run it locally, I do not.

SvelteKit intercepts fetch requests that could come from either the server or the client, and "simulates" CORS on the ones that run on the server. It's a good idea, because otherwise people could be thinking they're all good when their site works in SSR mode, but then when a user navigates and the same request is done as a CORS fetch via the browser, it could fail.

But in your situation, that check is falsely asserting that the simulated CORS check has failed. But... only on StackBlitz. There are some differences to how Undici and StackBlitz web containers handle fetches...

Here's SvelteKit making an SSR fetch via Undici:

{
  "user-agent": "undici",
  "accept": "*/*",
  "accept-encoding": "br, gzip, deflate",
  "accept-language": "*",
  "origin": "http://127.0.0.1:5173",
  "sec-fetch-mode": "cors",
  "x-forwarded-for": "47.196.22.77",
  "x-forwarded-host": "sveltekit.free.beeceptor.com",
  "x-forwarded-proto": "https"
}

And here's Firefox making one through StackBlitz:

{
  "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:108.0) Gecko/20100101 Firefox/108.0",
  "accept": "*/*",
  "accept-encoding": "gzip, deflate, br",
  "accept-language": "*",
  "cache-control": "no-cache",
  "origin": "https://corserrorduringserverrenderrepro-f5o4.w-corp.staticblitz.com",
  "pragma": "no-cache",
  "referer": "https://corserrorduringserverrenderrepro-f5o4.w-corp.staticblitz.com/",
  "sec-fetch-dest": "empty",
  "sec-fetch-mode": "cors",
  "sec-fetch-site": "cross-site",
  "te": "trailers",
  "x-forwarded-for": "47.196.22.77",
  "x-forwarded-host": "sveltekit.free.beeceptor.com",
  "x-forwarded-proto": "https"
}

But the weird thing is that both requests are getting the a "access-control-allow-origin": "*" response header... but the StackBlitz version thinks it is getting no access-control-allow-origin header.

Haffi921 commented 1 year ago

I have the same exact issue on a GitPod repo. I needed to disable csrf in svelte.config.js in order to continue working on it, and will need to remember resetting it in production. I guess I could something like csrf: env.prod === 'dev', but it feels weird to disable it completely during development.

Perhaps an ORIGIN=... variable in the .env file could solve this?

tctree333 commented 1 year ago

It might be an issue with the test endpoint in the example since httpbin returns an access-control-allow-origin that matches the origin (or is *). When an endpoint returns a proper access-control-allow-origin that's set to a domain, SvelteKit throws an error during server rendering (in dev and build). When I was trying it on StackBlitz it gave an error with httpbin but that might be a different issue considering that the header should be set.

If someone knows of a test endpoint that sets an access-control-allow-origin that might be a better test endpoint.

dummdidumm commented 1 year ago

It seems that the browser removes the access-control-allow-origin prior to handing it to the user's JavaScript - when doing response.headers.get('access-control-allow-origin'), it doesn't show up. I assume that Stackblitz and GitPod either have no control to forward these headers (because they can't access them either) or are removing them before passing it on.

We can't fix this without removing the sensible checks to ensure things run on the client and server the same, so I think the best we can do is document this. A workaround (which SvelteKit will warn you about, but you can ignore it in this case) is to use the global fetch in these cases.

maiertech commented 1 year ago

I have the same exact issue on a GitPod repo. I needed to disable csrf in svelte.config.js in order to continue working on it, and will need to remember resetting it in production. I guess I could something like csrf: env.prod === 'dev', but it feels weird to disable it completely during development.

Perhaps an ORIGIN=... variable in the .env file could solve this?

I am also developing on Gitpod and am running into this issue. I use the same workaround as @Haffi921.

itssumitrai commented 1 year ago

I am facing same issue. During production there would be different hosts, event.url.origin matches to a k8s container and not the user facing hostname. This creates CORS on server side because event.url.origin is never going to match user facing hostname, which BE api would be accepting in the origin header. We cannot use no-cors on server as universal fetch returns an empty body response which is not useful at all. We need a way to set the initial event.url.origin which is going to be used on all the cors requests going forward. Or better would be to not use 'cors' during server side rendering.

eltigerchino commented 1 year ago

@itssumitrai does the documentation below help resolve your issue? Does setting ORIGIN to your user facing hostname help? https://kit.svelte.dev/docs/adapter-node#environment-variables-origin-protocolheader-and-hostheader

maietta commented 6 months ago

Any updates?

maisonsmd commented 3 weeks ago

@eltigerchino

@itssumitrai does the documentation below help resolve your issue? Does setting ORIGIN to your user facing hostname help? https://kit.svelte.dev/docs/adapter-node#environment-variables-origin-protocolheader-and-hostheader

Sadly it doesn't work. What I don't understand is why fetch in +page.server.ts works, but not +page.ts during SSR 🙄

eltigerchino commented 3 weeks ago

What I don't understand is why fetch in +page.server.ts works, but not +page.ts during SSR 🙄

See #8314 (comment) for a full explanation. The recommended workaround for now is to use the global fetch function instead of the one provided by the load function.

maisonsmd commented 2 weeks ago

The recommended workaround for now is to use the global fetch function instead of the one provided by the load function.

Global fetch doesn't have access to cookies unfortunately, making authenticated requests impossible without passing cookies from +page.server.ts first.