redux 如何使用React和RTK查询处理JWT标记刷新周期:自动重取失败的查询

f87krz0w  于 2022-11-12  发布在  React
关注(0)|答案(3)|浏览(102)

在我的应用程序中,我有一个标记刷新端点/refresh,它在cookie中设置新的JWT刷新标记,并将新的JWT访问标记作为json发送回去。
由于访问令牌在5分钟后过期,因此我需要实现一个后台刷新逻辑,以便在访问令牌过期时启动。
目前我正在做的是通过RTK Query调用api,如果api拒绝查询,我就调用refreshTokens变体。
我需要把这个逻辑放在所有的api查询中,如下所示:

updateProfile(body)
      .unwrap()
      .catch((error) => {
        if (error && error.status === 403) {
          refreshTokens(null); // RTK Query Mutation
          updateProfile(body); // RTK Query Mutation
        }
      });

这样做似乎是代码的重复,因为它需要实现到所有对api的调用。
我想知道是否有一个全局解决方案可以在查询被拒绝时自动调用刷新令牌端点。

d8tt03nd

d8tt03nd1#

嗨,我已经做了一些与您所做的类似的事情,并且不需要对每个API调用重复代码,
我已经创建了我自己的fetch函数,它将接收两个参数,一个是调用fetch函数的函数,另一个是调用刷新令牌的函数。

const fetchOrRefresh = async (fetchFun, refreshFun) => {
  // first fetch call
  let fetchResponse = await fetchFun();
  let refreshResponse;

  // check if the call was ok, in my case api is returning and object that 
  // contains error object
  if (Object.keys(fetchResponse).includes("error")) {
    refreshResponse = await refreshFun();
  }

  // check if the refresh call was successful 
  if (refreshResponse && Object.keys(refreshResponse).includes("error")) {
  // if not ,, return anything or thow an error
    return null;
  }

  // recall the fetch and return what it returns
  return fetchFun();
};

也许您可以使用从错误消息中获得的返回信息来重构它,以便以更好的方式构建它,或者将它构建为中间件...

flvlnr44

flvlnr442#

注意:此解决方案在提取失败后自动刷新令牌,但不会自动重新提取最初失败的查询。因此,它仍然会导致代码重复。
这是我目前为止解决的方法:
1.在this doc的基础上创建了一个中间件来捕获宏级别的授权错误。它捕获授权错误并调用刷新JWT的突变,而不使用in this doc中解释的React钩子。此外,在RTK查询中间件之前附加错误处理中间件也很重要。

const jwtTokenRefresher =
  ({ dispatch }: Record<any, any>) =>
  (next: any) =>
  async (action: any) => {
    if (isRejectedWithValue(action)) {
      // Catch the authorization error and refresh the tokens
      if (action.payload.status === 403) {
        console.warn('We got a rejected action!');
        await dispatch(
          api.endpoints.refreshTokens.initiate(null)
        );
      }
    }

    return next(action);
  };

export const store = configureStore({
  reducer: {
//...
    [api.reducerPath]: api.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware()
      .concat(jwtTokenRefresher)
      .concat(api.middleware),
});

1.在我们通过中间件获得新的令牌后,我们再次调用try/catch块中的初始变异“无React钩子”。

update: build.mutation({
      query: (body) => ({
        url: //,
        method: 'POST',
        body,
      }),
      async onQueryStarted(
        arg,
        { dispatch, getState, queryFulfilled, requestId, extra, getCacheEntry }
      ) {
        try {
     //
        } catch (error: any) {
          if (error.error.status === 403) {
            await dispatch(api.endpoints.update.initiate(arg));
          }
        }
      },
    }),

到目前为止,它的工作正如我所希望的那样。我唯一关心的是需要捕捉所有突变上的错误,并再次调用突变。如果有人有任何想法来处理它的全球性以及我会很感兴趣。

cpjpxq1n

cpjpxq1n3#

在我最初的解决方案中,如果访问令牌过期,我可以重新获取JWT,但我无法为所有端点自动重新获取初始失败的查询。因此,我不得不为所有端点编写try-catch块。我不想编辑最初的答案,以免使解决方案看起来复杂,这也是一个有效的答案。
这是最终的解决方案,Redux RTK全局捕获授权错误并刷新JWT,然后自动重新获取初始失败的查询。
与我最初的答案相同的中间件,但这次refreshTokens不是一个突变,而是一个查询。

// middleware for redux store

const jwtTokenRefresher =
  ({ dispatch }: Record<any, any>) =>
  (next: any) =>
  async (action: any) => {
    if (action && isRejectedWithValue(action)) {
      // Catch the authorization error and refresh the tokens
      console.warn('We got a rejected action!', action.payload.status);
      // console.log({ action });
      if (action.payload.status === 403) {
        const { endpointName, originalArgs } = action.meta.arg;
        // console.log({ type, originalArgs });
        await dispatch(setRejectedAction({ endpointName, originalArgs }));
        await dispatch(backendApi.util.invalidateTags(['Tokens']));
      }
    }

    return next(action);
  };

1.我们通过.invalidateTags方法重新触发令牌刷新
1.我们从action.meta获取被拒绝查询的 meta数据,并将其保存到存储中,以便在刷新令牌后重新生成失败的查询。
然后,我们使用RTK查询的onQueryStarted方法等待令牌刷新完成:

// query inside createApi

refreshTokens: build.query({
      query: () => ({
        url: API_ROUTE.REFRESH_TOKENS,
        method: 'PATCH',
      }),
      providesTags: ['Tokens'],
      async onQueryStarted(
        arg,
        {
          dispatch,
          getState,,
          queryFulfilled,
          getCacheEntry
        }
      ) {
        await queryFulfilled;

        if (getCacheEntry().isSuccess) {
          const state = <Record<any, any>>getState();
          const {
            app: { rejectedAction = {} },
          } = state;
          const { endpointName, originalArgs } = rejectedAction;
          // console.log({ rejectedAction });
          const queryTrigger = <ThunkAction<any, any, any, any>>(
            backendApi.endpoints[
              endpointName as keyof typeof backendApi.endpoints
            ].initiate(originalArgs)
          );
          await dispatch(queryTrigger);
        }
      },
    }),
  1. await queryFulfilled确保令牌被刷新。
    1.通过const { endpointName, originalArgs } = rejectedAction;从存储中获取 meta数据
    1.并重新生成初始查询。
    多亏了这个流程,Redux RTK Query可以捕获所有失败的查询,刷新JWT,并自动重新获取失败的查询。

相关问题