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

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

Open
Arsh1a opened this issue May 16, 2023 · 2 comments
Open

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

Arsh1a opened this issue May 16, 2023 · 2 comments

Comments

@Arsh1a
Copy link

Arsh1a commented May 16, 2023

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
Copy link

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

@PSU3D0
Copy link

PSU3D0 commented Oct 26, 2023

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);
  }
}

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