Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use with JWT/RefreshToken? (and implement a retry strat) #157

Open
OClement opened this issue Apr 13, 2022 · 2 comments
Open

How to use with JWT/RefreshToken? (and implement a retry strat) #157

OClement opened this issue Apr 13, 2022 · 2 comments
Labels
📚 documentation Improvements or additions to documentation

Comments

@OClement
Copy link

sorry this is not an issue per se; but I couldn't find a satisfying answer anywhere else


Is there a proven strategy for the following?

  • User calls the api using a JWT auth
  • Authorizer rejects the token (eg: expired) and returns 401
  • App calls a refresh token api and renew the JWT
  • App re-execute the query that originally failed transparently

I've been toying with the Plugin API and could get up to the refresh part, but there doesn't seem to be a way to centralize the "retry" strategy in this case.

We're to the point where we are wrapping useQuery/useMutation to handle this in 1 place, but I was wondering if there's a better way? 🤔

Thanks

@logaretm
Copy link
Owner

Okay so at the moment I can't think of a solid implementation to show how we might go about implementing this. But quickly off the top of my head I can think of the following.


The plugin API could make this work but you must implement a custom fetch plugin here since you want to avoid setting the GQL result before retrying a couple of times and after you've successfully generated the token.

So starting off with the same codebase as the fetch plugin:

import { GraphQLError } from 'graphql';
// getActiveClient() isn't available yet but you can store the `Client` instance from `useClient` or `createClient` globally somewhere until #156 is merged
// you will also probably need some utils from `villus` to be exposed which we can expose in a separate PR
import { getActiveClient } from 'villus';

import { RefreshToken } from '@/graphql/Auth.gql'

// this is to make sure we don't duplicate `RefreshToken` calls
let refreshPromise = null;

export function fetchWithRetry(opts) {
   // same lines from 11 to 33
   // https://github.com/logaretm/villus/blob/main/packages/villus/src/fetch.ts#L11-L33
  
  // Change this to however you are going to figure out if the token is expired
  // maybe you can check the payload error message
  if (response.status === 401 && !refreshPromise) {
    client = getActiveClient();
    refreshPromise = client.executeMutation({ query: RefreshToken }).then(data => {
      // store the new token or auth data somewhere and set it on the fetchOpts
      setTimeout(() => {
         // clear up the promise, you don't want to do this immediately so toy around with the timeout to test it
          // maybe there is a more reliable way to clear it, but can't think of one at the moment
         refreshPromise = null;
      }, 100)
    });
  }

  if (response.status === 401 && refreshPromise) {
     await refreshPromise;
     //  re-execute the same code from lines 17 to 31
     // https://github.com/logaretm/villus/blob/main/packages/villus/src/fetch.ts#L17-L31
   }

   // same rest of the fetch function
}

This could need some clean up and some bug fixing. Anyways I think this is a good example worth adding to the docs I think, I will try to work on that on the weekend.

@quartze
Copy link

quartze commented Oct 21, 2023

We will need something like global error events. E.g. when creating a client, use the function useGlobalError and in it give a callback with the query used e.g. refetch()


const onError = ((error, refetch) => {
   if(error.code === 'UNAUTHORIZED') {
     // revoke token with refreshToken
     refetch();
    }
});

const client = createClient({
  url: '/graphql', // your endpoint.,
  errorHandle: onError
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📚 documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

3 participants