kit: Server-side `fetch` in `load` is not credentialed

Describe the bug No credentials are passed on in a server-side fetch.

To Reproduce On a fresh kit project:

<!-- src/routes/index.svelte -->
<script context="module">
  export async function load({ fetch }) {
    await fetch(`http://localhost:3000/test`, {
      credentials: "include",
      mode: "cors",
    });
    return true;
  }
</script>

<h1>blah</h1>
// src/routes/test.js
export async function get(request) {
  console.log(request);
  return {
    body: {
      data: 1234,
    },
  };
}
// src/setup/index.js
export async function prepare() {
  return {
    headers: {
      "Set-Cookie": "test=1234",
    },
  };
}

Load http://localhost:3000 twice.

Expected behavior Cookie header should appear in server console on second load.

Information about your SvelteKit Installation:

  System:
    OS: Windows 10 10.0.19042
    CPU: (16) x64 AMD Ryzen 7 3700X 8-Core Processor
    Memory: 12.38 GB / 31.95 GB
  Binaries:
    Node: 14.16.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.10 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 7.6.1 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Chrome: 89.0.4389.90
    Edge: Spartan (44.19041.423.0), Chromium (89.0.774.54)
    Internet Explorer: 11.0.19041.1
  npmPackages:
    @sveltejs/kit: next => 1.0.0-next.59
    svelte: ^3.29.0 => 3.35.0

Severity Blocking for any project that requires credentialed server-side requests.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 5
  • Comments: 30 (16 by maintainers)

Commits related to this issue

Most upvoted comments

This feels like a common use case for SvelteKit. I want to write my frontend in SvelteKit and have a separate api. What would be the optimal way to do this? Using token-based auth? Something else?

Reopening as this is not quite fixed. Internal fetches like fetch('/test') work.

“External” fetches like fetch('http://localhost:3000/test') (yes, same origin) do not have headers passed even if we explicitly include credentials:

await fetch('http://localhost:4000/test', {
  credentials: 'include',
  mode: 'cors'
});

Probably because we don’t pass it to the external node fetch: https://github.com/sveltejs/kit/blob/108c26cb797a7d09dda5f287cd17ee9f02bc914a/packages/kit/src/runtime/server/page.js#L82-L85

This is a blocker for me as well, as I don’t really understand how to interact with my backend while it’s not implemented within sveltekit and uses cookie-based authentication.

I don’t know how much workaround is it, but for now I’m using this approach:

myproject/src/routes/api/[…route].ts:

import fetch from 'node-fetch';
import type { RequestHandler } from '@sveltejs/kit';

const apiUrl = 'http://localhost:5000';

const handler: RequestHandler = async function (request) {
	const response = await fetch(apiUrl + request.path, {
		headers: request.headers,
		method: request.method
	});

	let body: unknown;

	try {
		body = await response.clone().json();
	} catch (e) {
		body = await response.text();
	}

	const headers = {
		'set-cookie': response.headers.get('set-cookie')
	};

	return {
		status: response.status,
		headers,
		body
	};
};

export const get: RequestHandler = handler;

This way I’m able to pass cookie back and forth, so this code kind of works:


<script lang="ts" context="module">
	import type { Load } from '@sveltejs/kit';
	export const load: Load = async (input) => {
		const resp = await input.fetch(`/api/auth/me`);
		const me = await resp.json();
		return {
			props: { me }
		};
	};
</script>

<script lang="ts">
	export let me: any;
</script>

{JSON.stringify(me)}

Doesn’t seem to me as an optimal approach, because in involves some manual parsing and non-obvious assumptions. Hope for some solution from @Rich-Harris as well.

I think this is beyond the scope of the original issue so I’ll file a new one specifically for same-origin “external” fetches.

I’d just like to add that I am also being frustrated by this bug.

A Graphql server on localhost:8080 and site on localhost:3000 and they don’t play nicely. In production I’d like to have the api on a subdomain.

These are pretty normal setups and should not be so difficult to get working.

I can’t understand why this issue is closed.

in order to move forward here a) (optional) node-fetch needs to accept the pull request https://github.com/node-fetch/node-fetch/pull/1116 b) sveltekit needs to actually set the cookie headers etc. if credentials is true. likewise to sapper

My problems are for situations where the domain is the same though. A cookie can be shared across subdomains or ports. That is not a browser limitation.

Edit: and yes, I’m also trying to use Apollo so need to use the full URL

i was able to make it work by adding this code here after line 90

if (uses_credentials) {
  //Get any cookie & authorization header from request and apply them to our internal fetch call
  opts.headers = Object.assign({}, opts.headers);
  
  const cookies = Object.assign(
	  {},
	  parseCookie(request.headers.cookie || '')
  );
  
  const str = Object.keys(cookies)
	  .map(key => `${key}=${cookies[key]}`)
	  .join('; ');
  
  opts.headers.cookie = str;
  
  if (!opts.headers.authorization && request.headers.authorization) {
	  opts.headers.authorization = request.headers.authorization;
  }
}

You also need to add

import { parse as parseCookie } from 'cookie';

at the beginning

then rebuild sveltekit via npm run build and copy the /dist/ssr.js over to your local npm folder

it works now and my server side rendering is actually sending the cookie credentials to the backend. However i am hesitant to open a Pull Request because i feel like i am not understanding 100% of the puzzle and don’t want to make any troubles (e.g. sapper is actually merging the request & response cookies (by looking at the SET-Cookie header from the response) but i don’t know how this can be done in sveltekit)

The original bug has been resolved. There’s not a way to know about what cookies exist on other domains during SSR. Supporting the Express middleware API was discussed internally and decided against. If they want, someone could write a userland interop module between the Express API and the handle hook.