Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Support trusted domains for SSR fetch with credentials #4750

Closed
MarcusCemes opened this issue Apr 26, 2022 · 7 comments · Fixed by #6565
Closed

[Feature Request] Support trusted domains for SSR fetch with credentials #4750

MarcusCemes opened this issue Apr 26, 2022 · 7 comments · Fixed by #6565
Labels
Milestone

Comments

@MarcusCemes
Copy link
Contributor

Describe the problem

TLDR: SRR fetching during data loading will not forward cookies to the API, if the API is not on a subdomain relative to SSR, with no reasonable option to override this behaviour. This is a delicate problem, as one small misconfiguration and the SSR server may accidentally forward client credentials to an unknown third party, effectively leaking their session key/token.

Currently, SSR fetch will only pass through the cookie header if the destination hostname has a suffix matching the SSR application's hostnames, these lines are responsible: This may be partially related to #1777.

// external fetch
// allow cookie passthrough for "same-origin"
// if SvelteKit is serving my.domain.com:
// - domain.com WILL NOT receive cookies
// - my.domain.com WILL receive cookies
// - api.domain.dom WILL NOT receive cookies
// - sub.my.domain.com WILL receive cookies
// ports do not affect the resolution
// leading dot prevents mydomain.com matching domain.com
if (
`.${new URL(requested).hostname}`.endsWith(`.${event.url.hostname}`) &&
opts.credentials !== 'omit'
) {
uses_credentials = true;
const cookie = event.request.headers.get('cookie');
if (cookie) opts.headers.set('cookie', cookie);
}

If the cookie is set with a domain of "example.com", the SvelteKit SSR is on "web.example.com" and the API at "api.example.com", client side data fetching will work, however, SSR will fail. If SSR is running on "example.com", credentials, notably the cookie header, will be passed on to the API.

I imagine that the main issue is that the client does not share domain/SameSite cookie information with the server, only the key/value payload itself, therefore SvelteKit not able to determine whether the cookie is allowed to be shared with the third party, even if it's on the same root domain. The only way to be sure is by checking if the fetch URL is on a subdomain of the received request, only then will the cookie's domain overlap with absolute certainty.

I noticed this when moving from a localhost environment to production, everything broke. Client-side data loading would work correctly, SSR data loading would be unauthenticated, creating confusing state conflicts and many hours of debugging... Someone else had the same experience a year ago.

I understand the rationale behind this, cookies and sessions are hard to keep secure at the best of times, but here the SSR behaviour diverges from the client data-loading behaviour. It's also a very difficult problem to solve, as there is no way to override this behaviour to the best of my knowledge.

Describe the proposed solution

Add an extra fetch parameter or credentials enum value to allow the credentials to be transmitted to the URL, such as credentials: "force". This would effectively be a way to tell SvelteKit/fetch hey, I can vouch for this request, you can send the credentials as well. I'm not sure if node-fetch would allow this, but I don't see why it shouldn't, as long as SvelteKit replaces the key with credentials: "include" and adds copies over the cookie header.

In my case, the SSR code is responsible for constructing the fetch URL from an environmental variable and a path anyway, seeing as it runs in a trusted environment, I would expect it to behave more like a reverse proxy than a browser fetch API with preflight requests and CORS checks (even though the API is already set up for CORS correctly, seeing as client-side fetch has no problem). I think adequate warning in the data-loading fetch documentation should be enough to inform users of the danger of leaking private cookies if the destination URL is not verified before calling fetch.

The above would likely pollute the fetch specification and types, a softer alternative would be to add a SvelteKit configuration option to effectively trust a certain domain (or array of domains), allowing for credentials to be forwarded as long as credentials is set to include, or at least not omit, a so-called safelist of API domains.

Alternatives considered

The getSession() hook has access to the client request, including headers, making it possible to extract the client cookie manually. This can be used to populate $session on every SSR request, similar to session_start() in PHP. Data loading is more suitable for injecting relevant data and is also able to hit the API directly from the client to avoid an extra round trip. This is an adequate solution for a small number of use cases, but comes at a relatively high cost when the user is using client-side routing, especially if Vercel limits serverless functions to a single region for the free tier. Computing on the edge solves this only partially.

Always using SvelteKit as the root domain, with the API on a subdomain. This is not always possible, for example, my university outright does not allow a subdomain depth of two or more, hence why I had to resort to...

... disable SSR data fetching seems to be the only viable solution, it's the only way to get consistent results, by letting the client fetch data dynamically and letting the browser enforce cookie policies correctly. This comes with the big downside of not having proper SSR, which goes against what SvelteKit is trying to achieve (a.k.a. Rich Harris' coined Transitional Apps term). For example, accessing Vercel without JS enabled will load the page, user-specific content, such as the Sign In button, is only faded in once client JS has run and determined the authentication state. It also complicates the client-side state to prevent subsequent flashes on navigation once the auth state is determined.

The cookie could be smuggled in another header, and forwarded on by SSR to the API that is configured to recognise/accept it, a so-called X-Forwarded-Cookie. But... what's the point? It also requires JS and HTTP-only to be disabled.

Importance

would make my life easier

Additional Information

No response

@mabentley85
Copy link

I'm in a similar boat where I have a SvelteKit app at dashboard.example.com that needs to communicate with api.example.com (this worked with Sapper). My current work around is creating a CNAME record api.dashboard.example.com.

This doesn't seem like an edge case scenario as I would think there are many cases were you may need different frontend apps fetching data from one API server.

@Rich-Harris Rich-Harris added the feature / enhancement New feature or request label May 12, 2022
@Rich-Harris Rich-Harris added this to the post-1.0 milestone May 12, 2022
@johnnysprinkles
Copy link
Contributor

Would this be fixed by #5195?

@MarcusCemes
Copy link
Contributor Author

Yes, I guess it would. If I understand correctly, that PR no longer automatically binds client cookies to the provided fetch() function, but also doesn't filter them anymore, so it should at least be possible to copy the Cookie header over manually when invoking a server-side fetch.

@johnnysprinkles
Copy link
Contributor

Yeah, it makes the whole incoming request available for your consideration. Actually the entire event, so you could even examine "locals" in case you set anything via hooks.

@rohanrajpal
Copy link

rohanrajpal commented Aug 27, 2022

Hey, i had created an issue related to this one today and it was marked as a duplicate
#6335

I'm confused as to how is it a duplicate? Shouldn't www.my.domain also pass cookies in SSR like my.domain does?

It's pretty standard to use www. and i don't think so people consider it a subdomain. Would appreciate some input!

@MarcusCemes
Copy link
Contributor Author

MarcusCemes commented Aug 27, 2022

It's not about whether people consider it a subdomain or not, www. is still a subdomain, and browsers will still apply the same cookie scoping/inheritance rules without exception. I agree that it's a popular pattern, but I don't think that it should be an implicit exception that might catch people off guard.

As www. is adjacent to api., SSR is unable to determine whether api. is allowed to see that cookie, or whether it would be leaking potentially sensitive cookies to an adjacent subdomain.

Even if the cookie is set to the domain itself, this information is not forwarded to SSR, hence SvelteKit takes the most conservative option of checking whether there is a strict domain hierarchy to be sure. It can't differentiate between a domain cookie and a www. scoped cookie, it only gets the value.

This issue is seeking to provide a way to configure SvelteKit to be able to tell it "hey, api. is safe, forward cookies, even if the browser wouldn't", which should resolve your issue by extension.

@rohanrajpal
Copy link

Ah yes, makes sense. Thanks a lot for clearing it out. Appreciate it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants