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

问题描述 投票:0回答:5

在我的应用程序中,我有一个令牌刷新端点

/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 的所有调用。

我想知道是否有一个全局解决方案可以在查询被拒绝时自动调用刷新令牌端点。

reactjs authentication redux jwt rtk-query
5个回答

0
投票

嗨,我做了一些与您所做的类似的事情,并且不需要对每个 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();
};

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


0
投票

注意:此解决方案在获取失败后自动刷新令牌,但不会自动重新获取初始失败的查询。所以还是会造成代码重复。

这就是我到目前为止解决问题的方法:

  1. 基于此文档创建了一个中间件来捕获宏观级别的授权错误。它捕获授权错误并调用刷新 JWT 的突变,而不使用 React hooks,这在本文档中进行了解释。在 RTK 查询中间件之前附加错误处理中间件也很重要。
  2. 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), });
通过中间件获取新令牌后,我们再次“不使用反应钩子”调用其 
    try/catch
  1. 块内的初始突变。
    
    
  2. 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)); } } }, }),
到目前为止,它的效果正如我所希望的那样。我唯一关心的是需要捕获所有突变的错误并再次调用突变。如果有人有任何想法在全球范围内处理它,我会很感兴趣。


0
投票

这是最终的解决方案,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);
  };

我们通过
    .invalidateTags
  1. 方法重新触发令牌刷新
    我们从
  2. action.meta
  3. 获取被拒绝查询的元数据并将其保存到存储中,以便在刷新令牌后重新生成失败的查询。
    
    
  4. 然后我们使用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);
        }
      },
    }),

    await queryFulfilled
  1. 确保令牌已刷新。
    通过 
  2. const { endpointName, originalArgs } = rejectedAction;
  3.  从商店获取元数据
    
    并重新生成初始查询。
  4. 得益于此流程,Redux RTK 查询会捕获所有失败的查询,刷新 JWT 并自动重新获取失败的查询。


0
投票
createApi

创建切片,然后您可以添加

processResponse
,它将在每次收到响应时运行,您可以在其中添加像
if (response.status === 403)
这样的检查并重新获取令牌
示例

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; const BASE_URL = 'https://example.app/api'; export const ApiSlice = createApi({ reducerPath: 'api', baseQuery: fetchBaseQuery({ baseUrl: BASE_URL, prepareHeaders: async (headers) => { const token = store.getState().auth.token; if (token !== null) { headers.set('Authorization', `Bearer ${token}`); } headers.set('Accept', 'application/json'); return headers; }, processResponse: async (response, meta) => { if (response.status === 403) { // Handle token expiration here // You can dispatch an action to refresh the token or redirect the user to login } return { data: await response.json() }; }, }), tagTypes: [], endpoints: (builder) => ({}), });

© www.soinside.com 2019 - 2024. All rights reserved.