Credentials example in handleFetch hook documentation is incorrect if a cookie was set by SvelteKit
Describe the bug
The documentation for handleFetch provides the following example for forwarding cookies to an API on a different domain than SvelteKit is hosted at:
/** @type {import('@sveltejs/kit').[HandleFetch](https://kit.svelte.dev/docs/types#sveltejs-kit-handlefetch)} */
export async function handleFetch({ request, fetch }) {
if (request.url.startsWith('https://api.yourapp.com/')) {
// clone the original request, but change the URL
request = new Request(
request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
request
);
}
return fetch(request);
}
Based on that, I created the following hook to additionally forward cookies from the API server to the client (since it appears that cookies are not forwarded on their own):
import envariant from '@knpwrs/envariant';
import type { HandleFetch } from '@sveltejs/kit';
const GRAPHQL_URL = envariant('GRAPHQL_URL');
const COOKIE_KEY = 'lcSessionId';
export const handleFetch: HandleFetch = async ({ event, request, fetch }) => {
const cookie = event.request.headers.get('cookie');
if (request.url.startsWith(GRAPHQL_URL) && cookie) {
request.headers.set('cookie', cookie);
}
const res = await fetch(request);
const cookieValue = res.headers
.get('set-cookie')
?.replace(`${COOKIE_KEY}=`, '');
if (cookieValue) {
event.cookies.set(COOKIE_KEY, cookieValue, {
sameSite: 'lax',
httpOnly: true,
path: '/',
});
}
return res;
};
The issue is that it appears that SvelteKit is URL-encoding the cookie value with event.cookies.set, but then not URL-decoding the cookie value when reading event.request.headers.get('cookie'). This makes sense as they're different APIs, but it leads to unintuitive bugs where a cookie set by SvelteKit is ultimately not sent to backend APIs in the same form it was set.
Reproduction
- Set a cookie with
- Attempt to forward that cookie using provided example in the documentation
Logs
No response
System Info
System:
OS: macOS 12.1
CPU: (10) arm64 Apple M1 Pro
Memory: 82.08 MB / 32.00 GB
Shell: 5.8 - /opt/homebrew/bin/zsh
Binaries:
Node: 18.10.0 - /nix/store/m3za3mblwg9az7vpkkvlfm04z66mcr6y-nodejs-18.10.0/bin/node
Yarn: 1.22.19 - /opt/homebrew/bin/yarn
npm: 8.19.2 - /nix/store/m3za3mblwg9az7vpkkvlfm04z66mcr6y-nodejs-18.10.0/bin/npm
Watchman: 2022.10.03.00 - /opt/homebrew/bin/watchman
Browsers:
Brave Browser: 106.1.44.101
Chrome: 107.0.5304.110
Chrome Canary: 99.0.4828.0
Firefox: 106.0.5
Safari: 15.2
npmPackages:
@sveltejs/adapter-auto: ^1.0.0-next.88 => 1.0.0-next.88
@sveltejs/kit: ^1.0.0-next.548 => 1.0.0-next.548
svelte: ^3.53.1 => 3.53.1
vite: ^3.1.0 => 3.2.4
Severity
annoyance
Additional Information
IIRC it isn't possible to set the set-cookie header in SvelteKit. Maybe it should be possible to set this header in hooks but not in page/layout handlers? That way backend cookies can be forwarded directly.
That example only uses the Header API, not the cookies API, so the example in the documentation should be correct. You are using a different API - the cookies API - afterwards to add a returned cookie back to the original event. This isn't part of the example in the docs.
If anything, we should probably enhance the example to show the way back, i.e. adding a cookie from the response back to the original event.
Some sort of official documentation on forwarding cookies would be great! Something about my .replace on the value of set-cookie feels kinda off.
This is still a major problem if you're trying to perform authentication requests (login/logout) to an external API. It could additionally open a major security hole by not respecting the original parameters that a cookie was set with, such as httpOnly or isSecure.
I am trying to use django as an api for auth, right now and i am stuck. Is it even technically possible to make it work securely?