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 update the access token during 401 response #60

Closed
aashan007 opened this issue Aug 20, 2021 · 3 comments
Closed

How to update the access token during 401 response #60

aashan007 opened this issue Aug 20, 2021 · 3 comments

Comments

@aashan007
Copy link

Hi guys,

I had a requirement where I want to intercept the response, when the token is expired using refresh token.

 response: async function (response) {
                if(response.status === 401)
                {
                   //call the refreshToken api
                   // update token
       
                   return fetch(url, config) //call fetch again with new token
                }
                else
                {
                    return response
                }        
            },

In the last release of the library, there was an update about request data passed with the response through which we can get the url. But how can I get the config details too. As there is no proper example or documentation on this, can anyone help me out here.

In the following open issue #39 , there is code provided but the implementation
wont work, in a case where there are multiple request been sent in same time.

Thanks,
:)

@gaspartripodiglobant
Copy link

gaspartripodiglobant commented Nov 29, 2021

@aashan007

It was not easy but we managed to come up with a solution so that 401 errors can be captured correctly. The solution supports simultaneous calls, which are saved until the token is refreshed.

What cannot be done from the library is to call back in the response interceptor the failed requests that have config, so it was solved by saving all the configs of the requests (don't worry, they are then deleted in the removeDataRequestsItem method) in an object in the request interceptor, to be able to be accessed in the response interceptor. A great addition to the library (which would save us having to do this step) would be to be able to access the initial config of the requests from the response interceptor.

This is the code (just use your refresh service, your REFRESH_URL and your logout method):

import fetchIntercept from 'fetch-intercept';
import { refresh, REFRESH_URL } from '...';

interface IDataRequest {
  [key: string]: Request;
}

export const CreateInterceptors = (() => {
  let instance;

  const init = () => {
    let isRefreshing = false;
    let refreshSubscribers = [];
    let dataRequests = {} as IDataRequest;

    const logoutUser = () => {
      console.log("logout method here");
    };

    const subscribeTokenRefresh = callback => {
      refreshSubscribers.push(callback);
    };

    const onRefreshed = () => {
      refreshSubscribers.map(callback => callback());
      refreshSubscribers = [];
    };

    const removeDataRequestsItem = requestKey => {
      const { [requestKey]: _omit, ...remaining } = dataRequests;
      dataRequests = remaining;
    };

    const getRelativeUrl = url => url.replace(window.location.origin, '');

    return {
      registerInterceptors: () => {
        fetchIntercept.register({
          request(url, config) {
            if (config && url.indexOf(REFRESH_URL) === -1) {
              dataRequests = {
                ...dataRequests,
                [`${getRelativeUrl(url)}_${config.method || 'GET'}`]: config,
              };
            }
            return [url, config];
          },

          response(response) {
            const requestKey = `${getRelativeUrl(response.url)}_${response.request.method}`;
            if (response.status === 401 && response.url.indexOf(REFRESH_URL) === -1) {
              if (!isRefreshing) {
                isRefreshing = true;
                refresh()
                  .then(() => {
                    isRefreshing = false;
                    onRefreshed();
                  })
                  .catch(() => logoutUser());
              }
              const retryOrigReq: any = new Promise((resolve, reject) => {
                subscribeTokenRefresh(() => {
                  fetch(response.url, {
                    ...dataRequests[requestKey],
                  })
                    .then(origReqResponse => {
                      resolve(origReqResponse);
                      removeDataRequestsItem(requestKey);
                    })
                    .catch(err => {
                      reject(err);
                    });
                });
              });
              return retryOrigReq;
            }
            removeDataRequestsItem(requestKey);
            return response;
          },
        });
      },
    };
  };

  return {
    getInstance() {
      if (!instance) {
        instance = init();
      }
      return instance;
    },
  };
})();

Then import it and call registerInterceptors:

import { CreateInterceptors } from '...';

const interceptors = CreateInterceptors.getInstance();
interceptors.registerInterceptors();

@aashan007
Copy link
Author

@gaspartripodiglobant Thanks mate

@ruifcnunes
Copy link

I solved this by applying a patch-package to fetch-intercept so that it will save the request (which already does) and the rawRequest (created by me - it will store the arguments [url, config] passed on the request intereceptor) on the response.rawRequest and error.rawRequest.

This way I always have access to the original config and original url.

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

No branches or pull requests

3 participants