reactjsnext.jsgraphqlapollo-clientgithub-graphql

How should I provide authentication for the Github graphql API when making client side queries?


I'm trying to make a client-side request to the GitHub graphql API using Apollo Client, but there's a 401 authentication issue I haven't been able to resolve.

I've swapped out the query with one to a different API that doesn't require authentication and have confirmed that it works as expected.

I can make successful requests server-side using getServerSideProps (it's a Next.js app), so I know the credentials are fine.

What am I missing here? Is this a CORS issue? (e.g. How to fix authentication error when querying Yelp GraphQL API using Apollo Client)

Thanks in advance. Code below, and reference to method here: https://www.apollographql.com/blog/apollo-client/next-js/next-js-getting-started/.


The handle for the client is defined in apollo-client.js:

import { ApolloClient, createHttpLink, InMemoryCache } from "@apollo/client";
import { setContext } from "@apollo/client/link/context";

export const getApolloClient = () => {
  const httpLink = createHttpLink({
    uri: "https://api.github.com/graphql",
  });

  const authLink = setContext((_, { headers }) => {
    return {
      headers: {
        ...headers,
        authorization: `Bearer ${process.env.GITHUB_ACCESS_TOKEN}`,
      },
    };
  });

  return new ApolloClient({
    link: authLink.concat(httpLink),
    cache: new InMemoryCache(),
  });
};

I've wrapped the root component in the Apollo context provider (pages/_app.jsx):

import { ApolloProvider } from "@apollo/client";
import { getApolloClient } from "../apollo-client";

const client = getApolloClient();

function MyApp({ Component, pageProps }) {
  return (
    <ApolloProvider client={client}>
      <Component {...pageProps} />
    </ApolloProvider>
  );
}

export default MyApp;

A sample query here in Component.jsx:

import { useQuery } from "@apollo/client";
import { gql } from "@apollo/client";

const QUERY = gql`
  query GetRepository {
    repository(owner: "facebook", name: "react") {
      id
      nameWithOwner
      description
      url
    }
  }
`;

const Component = () => {
  const { data, loading, error } = useQuery(QUERY);

  if (loading) {
    return (...);
  }

  if (error) {
    console.error(error);
    return null;
  }

  return <div>...</div>;
};

export { Component };

useQuery() returns "Response not successful: Received status code 401"


Solution

  • As @juliomalves pointed out, this issue can be solved by exposing the GITHUB_ACCESS_TOKEN to the browser, by prepending with NEXT_PUBLIC_. Since I didn't want the token exposed to the browser, the working solution was to make the request through a Next API route. The implementation closely follows the method at the link below for fetching data on the server:

    https://www.apollographql.com/blog/apollo-client/next-js/next-js-getting-started/

    I.e., in /pages/api/endpoint.js, something like:

    import { getApolloClient } from "../apollo-client";
    
    export default function handler(req, res) {
      const client = getApolloClient();
    
      try {
        const { data } = await client.query({
          query: gql`
            ...
          `,
        });
        res.status(200).json({ data });
      } catch (e) {
        res.status(400).json({ error: e.message });
      }
    }
    

    where the necessary authorization headers are defined in getApolloClient:

    import { ApolloClient, createHttpLink, InMemoryCache } from "@apollo/client";
    import { setContext } from "@apollo/client/link/context";
    
    export const getApolloClient = () => {
      const httpLink = createHttpLink({
        uri: "https://api.github.com/graphql",
      });
    
      const authLink = setContext((_, { headers }) => {
        return {
          headers: {
            ...headers,
            authorization: `Bearer ${process.env.GITHUB_ACCESS_TOKEN}`,
          },
        };
      });
    
      return new ApolloClient({
        link: authLink.concat(httpLink),
        cache: new InMemoryCache(),
      });
    };