score:0

So, I am using the @auth0/auth0-react and @apollo/client and I have managed to make it work as follows:

My app index.tsx:

<AuthProvider>
  <CustomApolloProvider>
    <Router>
      <MY_ROUTES>
    </Router>
  </CustomApolloProvider>
</AuthProvider>

Note: AuthProvider is just a alias for Auth0Provider for the purposes of this answer.

In CustomApolloProvider I have the following:

  1. Imports:
import React, { useEffect, useState } from 'react';
import { ApolloClient, ApolloProvider, InMemoryCache, HttpLink } from '@apollo/client';
import { useAuth0 } from '@auth0/auth0-react';
  1. Get auth context with useAuth0 and create client state:
  const { isAuthenticated, isLoading, getIdTokenClaims } = useAuth0();
  const [client, setClient] = useState(undefined as unknown as ApolloClient<any>)
  1. Trigger setClient when auth0 is ready:
  useEffect(() => {
    if (!isLoading && isAuthenticated) {
      // Here createApolloClient is a function that takes token as input
      // and returns `ApolloClient` instance. 
      getIdTokenClaims().then(jwtToken => setClient(createApolloClient(jwtToken.__raw)));
    }
  }, [isAuthenticated, isLoading]);
  1. Load page when client is available:
  if (!client)
    return <PageLoader />
  return (
    <ApolloProvider client={client}>
      {children}
    </ApolloProvider>
  );

A working example can be found on GitHub: https://github.com/atb00ker/ideation-portal/tree/1c6cbb26bb41f5a7b13a5796efd98bf1d77544cd/src/views

score:1

The main issue is that react hook can not be used outside function component. However, initialization of ApolloClient happens outside component and it requires access token to call backend graphql API which can be achieved by calling getTokenSilently() method. To solve this issue, I have exported the getTokenSilently() method manually (outside Auth0Provider).

For example:

import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "@auth0/auth0-spa-js";


const DEFAULT_REDIRECT_CALLBACK = () =>
  window.history.replaceState({}, document.title, window.location.pathname);

export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
let _initOptions, _client

const getAuth0Client = () => {
  return new Promise(async (resolve, reject) => {
    let client
    if (!client)  {
      try {

        client = await createAuth0Client(_initOptions)
        resolve(client)
      } catch (e) {
        console.log(e);
        reject(new Error('getAuth0Client Error', e))
      }
    }
  })
}

export const getTokenSilently = async (...p) => {
  if(!_client) {
      _client = await getAuth0Client()
  }
  return await _client.getTokenSilently(...p);

}

export const Auth0Provider = ({
  children,
  onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
  ...initOptions
}) => {
  const [isAuthenticated, setIsAuthenticated] = useState();
  const [user, setUser] = useState();
  const [auth0Client, setAuth0] = useState();
  const [loading, setLoading] = useState(true);
  const [popupOpen, setPopupOpen] = useState(false);

  useEffect(() => {
    const initAuth0 = async () => {
       _initOptions = initOptions;
       const client = await getAuth0Client(initOptions)
       setAuth0(client)
      // const auth0FromHook = await createAuth0Client(initOptions);
      // setAuth0(auth0FromHook);

      if (window.location.search.includes("code=")) {
        console.log("Found code")
        const { appState } = await client.handleRedirectCallback();
        onRedirectCallback(appState);
      }

      const isAuthenticated = await client.isAuthenticated();

      setIsAuthenticated(isAuthenticated);

      if (isAuthenticated) {
        const user = await client.getUser();
        setUser(user);
      }

      setLoading(false);
    };
    initAuth0();
    // eslint-disable-next-line
  }, []);

  const loginWithPopup = async (params = {}) => {
    setPopupOpen(true);
    try {
      await auth0Client.loginWithPopup(params);
    } catch (error) {
      console.error(error);
    } finally {
      setPopupOpen(false);
    }
    const user = await auth0Client.getUser();
    setUser(user);
    setIsAuthenticated(true);
  };

  const handleRedirectCallback = async () => {
    setLoading(true);
    await auth0Client.handleRedirectCallback();
    const user = await auth0Client.getUser();
    setLoading(false);
    setIsAuthenticated(true);
    setUser(user);
  };
  return (
    <Auth0Context.Provider
      value={{
        isAuthenticated,
        user,
        loading,
        popupOpen,
        loginWithPopup,
        handleRedirectCallback,
        getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
        loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
        getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
        getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
        logout: (...p) => auth0Client.logout(...p)
      }}
    >
      {children}
    </Auth0Context.Provider>
  );
};

Now, there is no restriction and we can call getTokenSilently() method either in function component or class component or any other place.

I have used following code to initialize ApolloClient and pass the client when calling ApolloProvider.

import React from "react";
import { Router, Route, Switch } from "react-router-dom";
import { Container } from "reactstrap";

import PrivateRoute from "./components/PrivateRoute";
import Loading from "./components/Loading";
import NavBar from "./components/NavBar";
import Footer from "./components/Footer";
import Home from "./views/Home";
import Profile from "./views/Profile";
import { useAuth0 } from "./react-auth0-spa";
import history from "./utils/history";
import "./App.css";
import { ApolloProvider } from '@apollo/react-hooks';
import initFontAwesome from "./utils/initFontAwesome";
import { InMemoryCache } from "apollo-boost";
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { ApolloLink, Observable } from 'apollo-link';
import { onError } from 'apollo-link-error';
import { withClientState } from 'apollo-link-state';
import {getTokenSilently} from "./react-auth0-spa";


initFontAwesome();


let API_URL="https://[BACKEND_GRAPHQL_API_URL]/graphql";

const cache = new InMemoryCache();
cache.originalReadQuery = cache.readQuery;
cache.readQuery = (...args) => {
  try {
    return cache.originalReadQuery(...args);
  } catch (err) {
    return undefined;
  }
};


const request = async (operation) => {
  const token = await getTokenSilently();
  operation.setContext({
    headers: {
      authorization: token ? `Bearer ${token}` : ''
    }
  });
};

const requestLink = new ApolloLink((operation, forward) =>
  new Observable(observer => {
    let handle;
    Promise.resolve(operation)
      .then(oper => request(oper))
      .then(() => {
        handle = forward(operation).subscribe({
          next: observer.next.bind(observer),
          error: observer.error.bind(observer),
          complete: observer.complete.bind(observer),
        });
      })
      .catch(observer.error.bind(observer));

    return () => {
      if (handle) handle.unsubscribe();
    };
  })
);



const client = new ApolloClient({
  link: ApolloLink.from([
    onError(({ graphQLErrors, networkError }) => {
      if (graphQLErrors) {
        console.log("Graphqlerrors"+graphQLErrors)
       // sendToLoggingService(graphQLErrors);
      }
      if (networkError) {
        console.log("Network error"+networkError)
       // logoutUser();
      }
    }),
    requestLink,
    withClientState({
      defaults: {
        isConnected: true
      },
      resolvers: {
        Mutation: {
          updateNetworkStatus: (_, { isConnected }, { cache }) => {
            cache.writeData({ data: { isConnected }});
            return null;
          }
        }
      },
      cache
    }),
    new HttpLink({
      uri: API_URL,
    // credentials: 'include'
    })
  ]),
  cache
});

const App = () => {
  const { loading } = useAuth0();

  if (loading) {
    return <Loading />;
  }

  return (
    <ApolloProvider client={client}>
    <Router history={history}>
      <div id="app" className="d-flex flex-column h-100">
        <NavBar />
        <Container className="flex-grow-1 mt-5">
          <Switch>
            <Route path="/" exact component={Home} />
            <PrivateRoute path="/profile" component={Profile} />
          </Switch>
        </Container>
        <Footer />
      </div>
    </Router>
    </ApolloProvider>
  );
};

export default App;

score:2

The way I tackled this issue is by editing an article I found online from https://hasura.io/

In other words, it uses react's useContext() hook and useEffect() to check and get the jwt token by using auth0's getTokenSilently() function.

I will just write the parts that are relevant:

import React, { FC, ReactNode } from 'react'
import { useAuth0 } from '@auth0/auth0-react'
import { ApolloProvider } from 'react-apollo'
import { ApolloClient, HttpLink, InMemoryCache } from 'apollo-boost'
import { setContext } from 'apollo-link-context'
import { useState, useEffect } from 'react'

const httpLink = new HttpLink({
  uri: 'yourdomain.test/graphql',
})

const Page: FC<{}> = ({children }) => {
  const [accessToken, setAccessToken] = useState('')
  const [client, setClient] = useState() as [ApolloClient<any>, any] // that could be better, actually if you have suggestions they are welcome
  const { getAccessTokenSilently, isLoading } = useAuth0()

  // get access token
  useEffect(() => {
    const getAccessToken = async () => {
      try {
        const token = await getAccessTokenSilently()
        setAccessToken(token)
      } catch (e) {
        console.log(e)
      }
    }
    getAccessToken()
  }, [])

  useEffect(() => {
    const authLink = setContext((_, { headers }) => {
      const token = accessToken
      if (token) {
        return {
          headers: {
            ...headers,
            authorization: `Bearer ${token}`,
          },
        }
      } else {
        return {
          headers: {
            ...headers,
          },
        }
      }
    })

    const client = new ApolloClient({
      link: authLink.concat(httpLink),
      cache: new InMemoryCache(),
    })

    setClient(client)
  }, [accessToken])

  if (!client) {
    return <h1>Loading...</h1>
  }

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

score:6

I had the same dilemma, especially since 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.


Related Query

More Query from same tag