Flyrell / axios-auth-refresh

Library that helps you implement automatic refresh of authorization via axios interceptors. You can easily intercept the original request when it fails, refresh the authorization and continue with the original request, without user even noticing.
MIT License
1.06k stars 91 forks source link

Problem using axios-auth-refresh with react-query #258

Open Arsh1a opened 1 year ago

Arsh1a commented 1 year ago

So if the Function that calls the refresh authorization fails, React-query isLoading will always be true.

Here is my code:

import axios from "axios";
import createAuthRefreshInterceptor from "axios-auth-refresh";

const axiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URI,
  withCredentials: true,
});

createAuthRefreshInterceptor(axiosInstance, (failedRequest) =>
  axiosInstance.get("/auth/access-token").then((res) => {
    return Promise.resolve();
  })
);

export const getData = async (url: string, withCredentials?: boolean) => {
  return await axiosInstance.get(process.env.NEXT_PUBLIC_API_URI + url);
};``

And this is how i use it using react-query

const { data: userData, isLoading,isError } = useQuery({
    queryKey: ["auth"],
    queryFn: () => getData("/users/me", true),
});

If I console log isLoading, it will be always true even tho isError is true, so the `isLoading `should be false.

billnbell commented 1 year ago

react-query has their own way of getting a 401 refresh token - use that.

PSU3D0 commented 1 year ago

For people that are running into this problem nowadays, there is a straight forward solution.

  1. The refresh callback needs to call Promise.reject(failedRequest) in order for react query to get this.
  2. This seems simple enough - add a catch to our refresh call in axios. If we hit the catch (refresh failed), then we reject the original failedRequest
  3. createAuthRefreshInterceptor 'catches' the subsequent failed request.

You'll want two axios clients. One that is wrapped in the createAuthRefreshInterceptor, and one that is not. Here is what we're using in our project.

export class AxiosHttpRequestWithRetry extends BaseHttpRequest {
  private refreshableAxiosInstance = axios.create();
  private refresherAxiosInstance = axios.create();

  private refreshAuthLogic = (failedRequest: any) => {
    const refreshUrl = this.config.BASE + '/jwt/refresh/';
    const refreshToken = getRefreshToken();

    if (!refreshToken) {
      return Promise.reject(failedRequest);
    }

    const result = this.refresherAxiosInstance // Notice this is the unwrapped client.
      .post(refreshUrl, {
        refresh: refreshToken,
      })
      .then((tokenRefreshResponse) => {
        localStorage.setItem('access', tokenRefreshResponse.data.access);
        failedRequest.response.config.headers['Authorization'] =
          'Bearer ' + tokenRefreshResponse.data.access;
        return Promise.resolve();
      }).catch((error) => {
        localStorage.removeItem('access');
        localStorage.removeItem('refresh');

        queryClient.invalidateQueries(['user']); // You'll probably want to invalidate your query that fetches user info

        return Promise.reject(failedRequest); // This causes react query to enter the failed state
      });

    return result;
  };

  constructor(config: OpenAPIConfig) {
    super(config);
    createAuthRefreshInterceptor(this.refreshableAxiosInstance, this.refreshAuthLogic);
  }

  public override request<T>(options: ApiRequestOptions): CancelablePromise<T> {
    return __request(this.config, options, this.refreshableAxiosInstance);
  }
}