How to use react-auth0-spa with GraphQL?

I’m using the react-auth0-wrapper class in the SPA demo and trying to use it with GraphQL. Specifically, how can I add the token to the GraphQL requests like in the GraphQL demo? The graphql creates an auth class that exposes getIdToken. The SPA wrapper doesn’t have that

const apollo = new ApolloClient({
  uri: 'http://localhost:3001/graphql',
  request: operation => {
    operation.setContext(context => ({
      headers: {
        ...context.headers,
        authorization: auth.getIdToken(),
      },
    }));
  }
})
3 Likes

Bumping this. Spent the last 2 days trying to figure out how to get tokens to my GraphQL provider. I am unable to do it because my ApolloClient isn’t in a functional component or class as all it does is return the client.

2 Likes

I meant to add that since my client isn’t made in a Functional Component/Class Component then I can’t call the React Hook that calls for the token.

I had a similar dilemma after following the Auth0 React quick start guide and trying to add Apollo Client. As @TanBeige pointed out it is tricky because the Auth0 hook can only be used from within a functional component but the docs seem to set up the ApolloProvider in the index file.

With a bit of experimentation I managed to get around this by creating a wrapper component which allows me to make use of the useAuth0 hook and asynchronously fetch/attach the token to each request.

I created a new file AuthorizedApolloProvider.tsx :

import { ApolloClient, ApolloProvider, createHttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/link-context';
import React from 'react';

import { useAuth0 } from '../react-auth0-spa';

const AuthorizedApolloProvider = ({ children }) => {
  const { getTokenSilently } = useAuth0();

  const httpLink = createHttpLink({
    uri: 'http://localhost:4000/graphql', // your URI here...
  });

  const authLink = setContext(async () => {
    const token = await getTokenSilently();
    return {
      headers: {
        Authorization: `Bearer ${token}`
      }
    };
  });
  
  const apolloClient = new ApolloClient({
    link: authLink.concat(httpLink),
    cache: new InMemoryCache(),
    connectToDevTools: true
  });

  return (
    <ApolloProvider client={apolloClient}>
      {children}
    </ApolloProvider>
  );
};

export default AuthorizedApolloProvider;

Then in my index.tsx file I wrap App with my new AuthorizedApolloProvider instead of using ApolloProvider directly.

ReactDOM.render(
  <Auth0Provider
    domain={config.domain}
    client_id={config.clientId}
    redirect_uri={window.location.origin}
    audience={config.audience}
    onRedirectCallback={onRedirectCallback}>

      <AuthorizedApolloProvider>
        <App />
      </AuthorizedApolloProvider>
      
  </Auth0Provider>,  
  document.getElementById('root')
);

Note: The above example is using Apollo Client 3 beta, and I had to install @apollo/link-context in addition to @apollo/client . I guess the required imports might be different for versions of Apollo Client.

2 Likes

Thank you @mattwilson1024 ! Do you know the difference between apollo-client and @apollo/client? And if this works with subscriptions as well?

Thanks,
Tan

My code example above is based on Apollo Client 3.0 (worth noting that at the time of writing it’s still in beta). In 3.0 they have changed the packages and made it so that you can import a lot more of the common functionality from a single package. So:

  • @apollo/client is for Apollo Client 3.0
  • apollo-client (plus various other packages) are for Apollo Client 2.x

See https://www.apollographql.com/docs/react/v3.0-beta/migrating/apollo-client-3-migration/#whats-new-in-30.

I’ve not tried subscriptions yet so not sure about that part I’m afraid

1 Like

No issues with the ApolloClient being recreated on each render?

Just to offer two more cents, I just got through dealing with this same issue. Namely, the Apollo client is created at the entry point, usually index.js in react which is handled before the Auth0Provider react wrapper is mounted in all of the examples.

For me the issue reared its head because I had to do the following:

  • Check for active session on page refresh or reload from navigating (usually handled with local storage of token in Apollo examples, which Auth0 notes as a security hole)
  • Handle authenticated calls to our GraphQL API with a valid token as part of the side effects in the Auth0Provider wrapper

Therefore, if the token was not available from local storage, the user would always be forced to login again before the token could be used in the Auth0Provider for api calls.

To get around this, I moved Auth0 client creation to the index file and wrapped Apollo client creation in a Promise so that first render would not occur before the client was available to make the getTokenSilently call and assign the token to a variable. This is similar to how @mattwilson1024 solved it with an intermediate wrapper, except it doesn’t require rendering before the client is available and therefore allows me to make corresponding API calls as needed in the Auth0Provider react component, with the added advantage of reduced async getTokenSilently() calls unless needed. It would also be possible this way to handle 401 errors with another Apollo Link to specifically redirect back to login easily.

import React from 'react';
import { render } from 'react-dom';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { CachePersistor } from 'apollo-cache-persist';
import createAuth0Client from '@auth0/auth0-spa-js';

/* Make sure auth0 client is available before AuthProvider gets assigned */
createAuth0Client({
  domain: /* YOUR DOMAIN */,
  client_id:  /* YOUR CLIENT ID */,
  audience: /* YOUR AUDIENCE */,
  redirect_uri: /* YOUR CALLBACK URL */,
}).then((auth0) => {
  const auth0Client = auth0;

  /* Set URI for all Apollo GraphQL requests (backend api) */
  const httpLink = new HttpLink({
    uri: /* BACKEND API URL */
    fetchOptions: { credentials: 'same-origin' },
  });

  /* Set in-memory token to reduce async requests */
  let token;

  /* Create Apollo Link to supply token with either
   * in-memory ref or auth0 req'ed token or redirect (built into
   * getTokenSilently
  */
  const withTokenLink = setContext(async () => {
    // return token if there
    if (token) return { auth0Token: token };

    // else check if valid token exists with client already and set if so
    const newToken = await auth0Client.getTokenSilently();
    token = newToken;
    return { auth0Token: newToken };
  });

  /* Create Apollo Link to supply token in auth header with every gql request */
  const authLink = setContext((_, { headers, auth0Token }) => ({
    headers: {
      ...headers,
      ...(auth0Token ? { authorization: `Bearer ${auth0Token}` } : {}),
    },
  }));

  /* Create Apollo Link array to pass to Apollo Client */
  const link = ApolloLink.from([withTokenLink, authLink, httpLink]);

  /* Set up local cache */
  const cache = new InMemoryCache({ fragmentMatcher });

  /* Create Apollo Client */
  const client = new ApolloClient({
    link,
    cache,
  });

  /* Create persistor to handle persisting data from local storage on refresh, etc */
  const persistor = new CachePersistor({ cache, storage: window.localStorage });

  /* Create root render function */
  const renderApp = (Component) => {
    render(
        <ApolloProvider client={client}>
          <AuthenticationProvider
            domain="YOUR DOMAIN"
            clientId="YOUR CLIENT ID"
            audience="YOUR AUDIENCE"
            redirectUri="YOUR REDIRECT URI"
            auth0Client="AUTH 0 CLIENT CREATED ABOVE"
          >
            <Component />
          </AuthenticationProvider>
        </ApolloProvider>,
      document.getElementById('root'),
    );
  };

  /* Render React App after hydrating from local storage */
  persistor.restore().then(() => {
    renderApp(App);
  });
});

2 Likes

Thanks for sharing it with the rest of community!

1 Like

We are using a SPA to interact with the API so the client should be a SPA client!

@tenrisab.o the client used in my example was the SPA client, unless I’m missing something in your reference. It is being imported from the ‘auth0-spa-js’ pkg just like the normal react-spa examples in auth0 and has the same methods. It is just that it is being created before initial render instead of after render, which is the main difference with the auth0 examples. The auth0 Provider in the examples can still be used, you just pass this client to it as prop as demonstrated instead of creating the client inside of it.

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.