Skip to content

Instantly share code, notes, and snippets.

@adamjmcgrath
Last active March 25, 2024 16:39
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save adamjmcgrath/0ed6a04047aad16506ca24d85f1b2a5c to your computer and use it in GitHub Desktop.
Save adamjmcgrath/0ed6a04047aad16506ca24d85f1b2a5c to your computer and use it in GitHub Desktop.

auth0-react static getAccessToken method

There are many use cases to use getAccessTokenSilently outside of a component (for example, in an Axios Interceptor or an Apollo Client).

It's tempting to ask for the option to pass an Auth0Client instance into the Auth0Provider so that its getTokenSilently method can used outside of the context of a component, eg.

const client = new Auth0Client();
export const getAccessToken = () => client.getTokenSilently();

ReactDOM.render(
  <Auth0Provider client={client}>
    <App />
  </Auth0Provider>,
  document.getElementById('app')
);

We don't expose the client or allow a custom client to be passed into the Auth0Provider for a couple of reasons:

  • We don't think you should export a singleton client from a module and risk sharing the autenticated state between requests in an SSR app - even though you might only be writing a client rendered app.
  • The client's methods don't always act the same as the hooks, eg the client's getTokenSilently behaves differently to the useAuth0 hook's getAccessTokenSilently, and this is the same (or could be the same in future) for other methods and properties. Having a client and hook in your app would lead to inconsistencies and subtly complex bugs.

Our recommended approach is to stick with React Context API, if you have an Apollo or Axios Client, create a provider for them and nest it in the Auth0Provider so it can have access to the useAuth0 hook to call getAccessTokenSilently eg.

ReactDOM.render(
  <React.StrictMode>
    <Auth0Provider>
    {() => {
      const { getAccessTokenSilently } = useAuth0();
      const instance = axios.create()
      instance.interceptors.request.use(function () {/* use getAccessTokenSilently */});
      return (
        <AxiosProvider axios={instance}>
          <App />
        </AxiosProvider>)
    }}
  </Auth0Provider>
  </React.StrictMode>,
document.getElementById('root')
);

If you really have to break out of React Context, you can always create a static getAccessToken method (with the above caveats) - you don't need a static client.

const deferred = (() => {
  const props = {};
  props.promise = new Promise((resolve) => props.resolve = resolve);
  return props;
})();

export const getAccessToken = async () => {
  const getToken = await deferred.promise;
  return getToken();
} 

ReactDOM.render(
  <Auth0Provider clientId={clientId} domain={domain}>
    <Auth0Context.Consumer>
      {({ getAccessTokenSilently }: any) => {
        deferred.resolve(getAccessTokenSilently);
        return <App />;
      }}
    </Auth0Context.Consumer>
  </Auth0Provider>,
  document.getElementById('app')
);
@gentgaashi
Copy link

In the AxiosProvider example, would it be a bad idea to create an axios instance outside on some other file and then add the interceptor to that, instead of creating a new instance like in the example, and use that axios instance to make requests instead of having to rely on the context value? i know it is not good practice since it relies on the AuthProvider modifying an instance outside of it, but could it cause any weird bugs?

Something along the lines of:

import customAxios from '../../customAxios';

ReactDOM.render(
  <React.StrictMode>
    <Auth0Provider>
    {() => {
      const { getAccessTokenSilently } = useAuth0();
      customAxios.interceptors.request.use(function () {/* use getAccessTokenSilently */});
      return (
        <AxiosProvider>
          <App />
        </AxiosProvider>)
    }}
  </Auth0Provider>
  </React.StrictMode>,
document.getElementById('root')
);

@giovanniantonaccio
Copy link

Is it expected that you get a different token every time you call getAccessTokenSilently from outside of React Context?
I thought that the getAccessTokenSilently would only return a different token when the current one expires.

@a-type
Copy link

a-type commented Mar 25, 2024

Both of these suggested approaches are confusing, particularly for developers not as experienced with the nuances of React, as I'm sure you've found to be the case with support conversations. I've recently begun a new position at a company which uses Auth0 and found myself linked back to this gist, so the problem is now relevant to me again. I've even seen developers implementing a custom cache which they break open to retrieve raw tokens in order to work around these constraints, which leads to very brittle code entirely dependent on internal library details. These constraints have unintended consequences in users' code.

What I find particularly notable is how no other SDK which relies on a 'client' concept takes this convoluted approach, even though all of them have SSR support.

Apollo is a good example. Rather than force the user to jump through hoops, they instead design particularly for SSR with their client itself and document how to incorporate it: https://www.apollographql.com/docs/react/performance/server-side-rendering/. Practically any React SDK which utilizes a "client" concept has an SSR page in its docs that looks like this.

What I feel the Auth0 team hasn't quite grasped, while citing Dan's 2015 Stack Overflow post, is that server-rendered clients will have a different "root" than client-rendered versions, which makes it fairly straightforward to initialize a request-scoped client cache and pass it to the server-side Provider, meanwhile having the client-side entry point render with a singleton client passed to its own Provider, no problem. Dan was only discouraging relying on a singleton from within your React tree itself, not having a 'singleton' client instance available at all. That's why Redux, which he was talking about, still has a store concept available to the user in module scope, and recommends similar practices as Apollo for SSR (nothing anything like what you're suggesting here appears in Redux or Apollo recommendations, despite almost identical problems).

Admittedly, things get more complicated with stuff like the latest NextJS with server rendered components, as one can see with Apollo's attempt to support it: https://www.apollographql.com/blog/how-to-use-apollo-client-with-next-js-13. At the end of the day, though, Apollo and Redux itself are good examples of libraries really trying to integrate well with the tools they support, not pushing it off on the user to use odd patterns like assigning global values from inside the React lifecycle.

I suppose your second point of reasoning probably stands, but I'm not sure the principle of it is particularly compelling compared to the amount of jank required in Auth0-based React codebases as a result. All you really have to do is put a note in your docs which says "calling this method from React hooks behaves differently to calling it on your client in X, Y, Z ways." Users will prefer this to having to dig up gists like this in order to use Auth0 with basically any app which uses RTKQuery, Apollo, React Query, or other popular client querying tools.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment