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

useSuspenseQuery not sending cookies on Server Side query #85

Open
ODreelist opened this issue Aug 13, 2023 · 17 comments
Open

useSuspenseQuery not sending cookies on Server Side query #85

ODreelist opened this issue Aug 13, 2023 · 17 comments

Comments

@ODreelist
Copy link

ODreelist commented Aug 13, 2023

Hi there,

I may be missing something simple here but I have an apollo-provider that is pretty standard, however we use supabase auth and the session is set in cookies.

Obviously the cookie is present when the user refreshes the query page, but its not until the client re-initiates the query (which I don't think would happen if this issue was solved) that the server sees the cookies passed. The httpLink and client set up is as follows:

`
const isServer = typeof window === "undefined";
const httpLink = new HttpLink({
uri: GRAPHQL_URI,
credentials: "include"
});

return new NextSSRApolloClient({
cache: apolloCache,
link: isServer
? ApolloLink.from([new SSRMultipartLink({ stripDefer: true }), httpLink])
: httpLink,
});
};
`

I know the cookie is available because I can set it as an authorization header which does get fired in the query on the server side but I'd rather not have to "hack" it that way, Is there a way to ensure that even when query is processed on the server side that it includes the cookies?

Thanks in advance.

@plausible-phry
Copy link

The problem is that the server does per default not have any connection between cookies of the incoming request (browser to Next server) and outgoing requests (server to GraphQL server).

You will have to extract the cookie from the incoming request and then manually pass it on.

Unfortunately, in Next.js, Client Components cannot access the cookies of the incoming request - only server components can.
So you need to:

  • in a server component call cookies()
  • extract the cookie you need
  • pass it as props to a Client Component (your ApolloProviderWrapper)
  • if isServer, manually add that cookie to your HttpLink.

@ODreelist
Copy link
Author

I appreciate the thoughtful response, that's essentially my current implementation, except I manually add the token from the cookie to the HttpLink via headers.authorization. Is that what you mean? Or is there a way to actually manually add the cookie to the HttpLink that I'm not aware of.

@plausible-phry
Copy link

Is that what you mean?

I'd pass it into the HttpLink constructor via headers.authorization option, so probably what you are doing now :)

@rval
Copy link

rval commented Oct 7, 2023

pass it as props to a Client Component (your ApolloProviderWrapper)

Forgive me if I'm missing something obvious, but if you have an HTTP-only cookie that holds a sensitive credential, and then pass it to a client component, don't you risk exposing it to client-side code and introducing an XSS vulnerability?

Is the thinking here that unless you're actually rendering the cookie value it shouldn't show up in any part of the browser payload? Edit: looks like props passed to client components can show up in the browser payload, even if they're not used directly:

self.__next_f.push([1,"c:I{\"id\":\"(app-client)/./src/app/org/Name.tsx\",\"chunks\":[\"app/org/page:static/chunks/app/org/page.js\"],\"name\":\"\",\"async\":false}\nb:[[\"$\",\"h1\",null,{\"children\":[\"Hello, \",\"Michael Bluth\",\"!\"]}],[\"$\",\"$Lc\",null,{\"secret\":\"MY_SUPER_SECRET_VALUE\"}]]\n"])

@phryneas
Copy link
Member

phryneas commented Oct 9, 2023

@rval I'd love to give you a better answer, but this seems like an architectural oversight on the side of Next.js - we won't be able to give you any better advice until they come up with a better way of doing this.

Maybe open an issue over there, and if they come up with something better, please report back here? I'd love to hear about that :)

@Stevemoretz
Copy link

Stevemoretz commented Oct 9, 2023

pass it as props to a Client Component (your ApolloProviderWrapper)

Forgive me if I'm missing something obvious, but if you have an HTTP-only cookie that holds a sensitive credential, and then pass it to a client component, don't you risk exposing it to client-side code and introducing an XSS vulnerability?

Is the thinking here that unless you're actually rendering the cookie value it shouldn't show up in any part of the browser payload? Edit: looks like props passed to client components can show up in the browser payload, even if they're not used directly:

self.__next_f.push([1,"c:I{\"id\":\"(app-client)/./src/app/org/Name.tsx\",\"chunks\":[\"app/org/page:static/chunks/app/org/page.js\"],\"name\":\"\",\"async\":false}\nb:[[\"$\",\"h1\",null,{\"children\":[\"Hello, \",\"Michael Bluth\",\"!\"]}],[\"$\",\"$Lc\",null,{\"secret\":\"MY_SUPER_SECRET_VALUE\"}]]\n"])

Here's what I did, make a new env variable name it anything you'd like but don't start it with "NEXT_PUBLIC_" (otherwise that will be bundled in browser) for instance I chose : ENCRYPTION_KEY="12345678"

Now before passing your cookies to a child client component encrypt it using that key in your layout file, in your client component (ApolloWrapper) decrypt it only if typeof window === "undefined" and attach it.

Since no one will have access to your encryption key all your cookies will be encrypted and decrypted safely on the server side, but remember to add some random data into your object before encryption otherwise it might still be easy to crack.

It's not the best we could do but it's probably the best we can do, if NextJS team added some global request object for the server side, we didn't have to deal with all these non-sense.

@phryneas
Copy link
Member

That's incredibly hacky, but also a great solution - well done!

@iamkd
Copy link

iamkd commented Oct 12, 2023

We have encountered the same issue and it is so frustrating. Basically it blocks SSR support for hooks (unless we use a really smart but still hacky solution above). I have created a discussion in the Next.js repo, hopefully it will gain some attention.

@phryneas
Copy link
Member

phryneas commented Jan 4, 2024

@Stevemoretz I went ahead and published your approach as a npm package that should make this a lot easier to utilize: https://www.npmjs.com/package/ssr-only-secrets

@esavitskiy
Copy link

esavitskiy commented Feb 19, 2024

import { setContext } from '@apollo/client/link/context';

const forwardCookieLink = setContext(async () => {
  return import('next/headers').then(({ cookies }) => {
    return {
      headers: {
        cookie: cookies()
          .getAll()
          .map(({ name, value }) => `${name}=${value}`)
          .join(';'),
      },
    };
  });
});

return new NextSSRApolloClient({
    // use the `NextSSRInMemoryCache`, not the normal `InMemoryCache`
    cache: new NextSSRInMemoryCache(),
    link:
      typeof window === "undefined"
        ? ApolloLink.from([
            // in a SSR environment, if you use multipart features like
            // @defer, you need to decide how to handle these.
            // This strips all interfaces with a `@defer` directive from your queries.
            new SSRMultipartLink({
              stripDefer: true,
            }),
            forwardCookieLink,
            httpLink,
          ])
        : httpLink,
  });

@phryneas
Copy link
Member

phryneas commented Feb 19, 2024

@esavitskiy You cannot use cookies() outside of React Server Components, and NextSSRInMemoryCache is explicitly targeting Client Components (and this whole thread is about SSR of Client Components), so this seems like it wouldn't do what you expect.

@esavitskiy
Copy link

@esavitskiy You cannot use cookies() outside of React Server Components, and NextSSRInMemoryCache is explicitly targeting Client Components (and this whole thread is about SSR of Client Components), so this seems like it wouldn't do what you expect.

just try

@phryneas
Copy link
Member

image

It seems that this is working in some way, but it is clearly not documented and might break with every update.

@phryneas
Copy link
Member

I just verified with the Next.js support forum. This is not a stable feature of Next.js. Please don't do this.

image

@esavitskiy
Copy link

This is not a stable feature of Next.js. Please don't do this.

I agree, but it would be nice =)

@Froncz
Copy link

Froncz commented May 13, 2024

Is there any update on this?

@phryneas
Copy link
Member

phryneas commented May 13, 2024

@Froncz I believe this has been answered, so what is your question after reading all the comments?

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

No branches or pull requests

8 participants