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 authenticated queries with SSR #166

Open
zenflow opened this issue Mar 28, 2021 · 2 comments
Open

feature request: support authenticated queries with SSR #166

zenflow opened this issue Mar 28, 2021 · 2 comments

Comments

@zenflow
Copy link

zenflow commented Mar 28, 2021

What I mean by "authenticated queries" is graphql queries that have an authentication cookie or http header.

For a regular client-side SPA, it's easy to authenticate queries by modifying your queryFetcher. You can get the auth token from the browser environment & include it in the fetch call:

  const queryFetcher: QueryFetcher = async function (query, variables) {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
+       Authorization: `Bearer ${window.localStorage["auth-token"]}`,
      },
      body: JSON.stringify({ query, variables }),
      mode: "cors",
    });
    const json = await response.json();
    return json;
  };

(or use fetch option credentials: 'include' to have http cookies included automatically)

For a server-side rendering, the queryFetcher needs to know about the page request (req), so that it can forward the auth token that the browser sent in the page request:

+ const isServerSide = typeof window === "undefined";
  const queryFetcher: QueryFetcher = async function (query, variables) {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
+       Authorization: isServerSide
+         ? req.headers.Authorization
+         : `Bearer ${window.localStorage["auth-token"]}`,
      },
      body: JSON.stringify({ query, variables }),
      mode: "cors",
    });
    const json = await response.json();
    return json;
  };

The objective becomes getting the req object in scope of queryFetcher. It's currently possible, by creating one client per page request (which makes sense anyway), but currently that's not ergonomic and comes with limitations.

I have pushed a demo project for reference: repo / deployment

In this demo, the components, instead of using a global singleton client, use the client we "provide" via React context. This way the component definitions are not tied to a single client. This is a common approach, used by apollo & urql & more.

The problem is that since there is no native GqlessContext in the @gqless/react, which the hooks & HOC would use to read client from context, we need to wrap the whole react client to make the hooks read client from context.

To avoid changing my components I created an adapted useQuery hook to (1) use the useContext hook to get the gqless clients (main client & react client) from context, & (2) call the hook and returns the result.
The alternative would be to have a single hook that returns the entire gqless react client, which could be used like const { posts } = useGqless().useQuery(). This is not really pretty, or a simple/straightforward change to the codebase, just to add that http header.

Also, using the graphql HOC becomes awkward since it's only accessible from within another component.

Recommendation

The ReactClient should include a GqlessContext: React.Context<GqlessClient<GeneratedSchema>> member which is consumed by the hooks & hoc. This would allow us to provide the client using <GqlessContext.Provider value={client}>, or not do that (the client param passed to createReactClient should be used as the default value), and use the same hooks & HOC either way.

@zenflow
Copy link
Author

zenflow commented Mar 28, 2021

@PabloSzx Let me know if you are interested in a PR implementing my recommendation. I think it may solve the web of SSR issues I opened: this one & #165 for sure, & probably #168, which would in turn unblock the getInitialProps solution to #167

@PabloSzx
Copy link
Contributor

PabloSzx commented Mar 28, 2021

This specific feature is a tough one, as I mentioned in #168 (comment), using getInitialProps is completely possible to implement this feature, and if you want to propose a implementation around the existing code (after #169 is merged), I would be very grateful, and maybe add it in the docs as a copy-paste in your project solution, or add it in the gqless-react packages as scoped module "@gqless/react/nextjs" or something like that, but I don't think it should be added in the main module, since it would be a framework-specific solution

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

2 participants