Auth0-react how to use getAccessTokenSilently for my urql auth-exchange?

Hi, I am using urql graphql library together with auth0-react.

According to their documentation for auth-exchange
I need to get the token on every call.

so, a simplistic graphqlClient looks like this

// index.js

import { Auth0Provider } from '@auth0/auth0-react'
import { createClient, Provider as UrqlProvider, dedupExchange, cacheExchange, fetchExchange, makeOperation } from 'urql'
import { authExchange } from '@urql/exchange-auth'

const graphClient = createClient({
  url: `${CONFIG.API_BASE_URL}/authenticated/graphql`,
  requestPolicy: 'network-only',
  exchanges: [
    dedupExchange,
    cacheExchange,
    authExchange({
    /* auth config */
    }),
    fetchExchange]
})

const Base = () => {
  return (
  <Router history={history}>
      <Auth0Provider
        domain={CONFIG.AUTH0_DOMAIN}
        clientId={CONFIG.AUTH0_CLIENT_ID}
        audience={CONFIG.AUTH0_AUDIENCE}
        connection={CONFIG.AUTH0_CONNECTION}
        redirectUri={CONFIG.AUTH0_REDIRECT_URI}
        logoutUri={CONFIG.AUTH0_LOGOUT_URI}
        cacheLocation={navigator.userAgent.indexOf('Safari') > 0 ? 'localstorage' : 'memory'}
        scope='openid profile email offline_access me:myapp'
        useRefreshTokens={true}
      >
        <UrqlProvider value={graphClient}>
            <GlobalStyle />
            <App />
        </UrqlProvider>
      </Auth0Provider>
  </Router>
  )
}

ReactDOM.render(<Base />, document.getElementById('root'))

Now the problem: auth config, on another project (that’s not by me) … it can import the auth0client and does this:

authExchange({
getAuth: async ({ authState }) => {
  if (!authState) {
    try {
      const token = await auth0Client.getTokenSilently()
      return { token }
    } catch (error) {
      console.error('auth error', error)
      return null
    }
  }
  try {
    const newToken = await auth0Client.getTokenSilently()
    if (newToken) {
      return { token: newToken }
    } 
  } catch(error) {
    console.error('auth error', error)
    return null
  }
},
addAuthToOperation: ({ authState, operation }) => {
  // the token isn't in the auth state, return the operation without changes
  if (!authState || !authState.token) {
    return operation
  }

  const fetchOptions =
  typeof operation.context.fetchOptions === 'function'
  ? operation.context.fetchOptions()
  : operation.context.fetchOptions || {}

  const op = makeOperation(
    operation.kind,
    operation,
    {
      ...operation.context,
      fetchOptions: {
        ...fetchOptions,
        headers: {
          ...fetchOptions.headers,
          "Authorization": `Bearer ${authState.token}`,
          "Accept": 'application/json'
        }
      }
    }
  )
  return op
},
}),

The auth0Client is created from:

import { Auth0Client } from '@auth0/auth0-spa-js'

const auth0Props = {
  domain: CONFIG.AUTH0_DOMAIN,
  client_id: CONFIG.AUTH0_CLIENT_ID,
  audience: CONFIG.AUTH0_AUDIENCE,
  connection: CONFIG.AUTH0_CONNECTION,
  redirect_uri: CONFIG.AUTH0_REDIRECT_URI,
  logout_uri: CONFIG.AUTH0_LOGOUT_URI,
  cacheLocation: navigator.userAgent.indexOf('Safari') > 0 ? 'localstorage' : 'memory',
  scope: 'openid profile email offline_access me:myapp',
  useRefreshTokens: true
}

const auth0Client = new Auth0Client(auth0Props)

How can I use auth0-react and use the getTokenSilently() outside the components?

I’ve seen some similar discussions

Most ended up making their own context.

Thanks for looking into this!

Hey @vinamelody ,

sorry for the late reply. For anyone stumbling into the urql-with-auth0 challenge, you can use the following code to create a HOC AuthorizedUrqlProvider. Keep in mind this is Typescript, so you might need to get rid of the types (x:>>any<<) stuff. You probably need to include wonka in your project, as urql heavily relies on it :slight_smile: .

import React from 'react';
import { useAuth0 } from '@auth0/auth0-react';
import { createClient, Provider, dedupExchange, cacheExchange, fetchExchange, Exchange, Operation } from 'urql';
import { pipe, map, mergeMap, fromPromise, fromValue } from 'wonka';

const AuthorizedUrqlProvider: React.FC = ({ children }) => {
  const { getAccessTokenSilently } = useAuth0();

  const fetchOptionsExchange = (fn: any): Exchange => ({ forward }) => (ops$) => {
    return pipe(
      ops$,
      mergeMap((operation: Operation) => {
        const result = fn(operation.context.fetchOptions);
        return pipe(
          (typeof result.then === 'function' ? fromPromise(result) : fromValue(result)) as any,
          map((fetchOptions: RequestInit | (() => RequestInit)) => ({
            ...operation,
            context: { ...operation.context, fetchOptions },
          })),
        );
      }),
      forward,
    );
  };

  // want to debug? -> https://github.com/FormidableLabs/urql-devtools-exchange#usage
  const client = createClient({
    url: process.env.REACT_APP_APOLLO_GATEWAY || 'http://localhost:4200/',
    exchanges: [
      dedupExchange,
      cacheExchange,
      fetchOptionsExchange(async (fetchOptions: any) => {
        const token = await getAccessTokenSilently();

        return Promise.resolve({
          ...fetchOptions,
          headers: {
            Authorization: token ? `Bearer ${token}` : '',
          },
        });
      }),
      fetchExchange,
    ],
    requestPolicy: 'network-only',
  });

  return <Provider value={client}>{children}</Provider>;
};

export default AuthorizedUrqlProvider;
2 Likes

Thanks for sharing it with the rest of community!

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