How to use react-auth0-spa with GraphQL?

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