Using refresh token in graphQL client-side when token has expired

Viewed 3593

The scenario is:

  1. Web app is open but token has expired.
  2. User then does some action that makes an api request.
  3. GraphQL returns Error: GraphQL error: unauthorized.
  4. The app doesn't respond due to the error. Bad user experience.
  5. onError from apollo-link-error method catches the error and from this am able to call refreshToken() (it requires an expired jwt).
  6. New token is generated and stored for use.
  7. Next user action will go through as per normal.

This is currently my code:

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    refreshToken();
  }
});

const link = ApolloLink.from([errorLink, terminatingLink]);

How can we improve this flow so that the token is refreshed when the user makes a request without getting an error? Something like the request is 'put on hold' if it causes an error then once token is refreshed, then it will be processed. But I am not sure how to do this.

Or is there any other way we can improve on this flow?

1 Answers

You can create a Link that can be ran before the operation is executed via your terminating link. You can create your own stateless link but you can also get away with using setContext, which can be used to set your request headers while we're at it.

Since you're using a JWT, you can decode it on the client side to extract the expiration value and use that to determine if you need to refresh the token before the request is sent to the server. If you weren't using a JWT but the server returned an expiration time with the token, the same strategy would apply.

const contextLink = setContext(async () => {
  const { exp } = jwtDecode(jwt)
  // subtracts a minute to account for latency
  const expirationTime = (exp * 1000) - 60000
  if (Date.now() >= expirationTime) {
    await refreshToken()
  }
  return {
    // you can set your headers directly here based on the new token
    headers: {
      ...
    }
  }
})
Related