如何使用 RTK 查询“刷新令牌”

Cod*_*der 0 reactjs react-redux rtk-query

我有一组用 Redux Toolkit 编写的 API 调用。例如:

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { Contacts } from '../datamodel/Contact';
import { getAccessToken } from '../util/userContextManagement';

export const contactsApi = createApi({
    reducerPath: 'contactsApi',
    baseQuery: fetchBaseQuery({ baseUrl: '/api/users/current' }),
    endpoints: builder => ({
        getContacts: builder.query<Contacts, void>({
            query: () => {                
                return ({
                  url: '/contacts',
                  method: 'GET',
                  headers: { Authorization:  `Bearer ${getAccessToken()?.token}`}
                });
              },
        })
    })
})

export const { useGetContactsQuery } = contactsApi
Run Code Online (Sandbox Code Playgroud)

我可以使用函数注入访问令牌:getAccessToken()

但是,我想在函数中检测到访问令牌已过期,并在函数返回之前使用另一个 API 调用刷新它。

不幸的是,我无法在这个函数中执行此操作,因为getAccessToken()不是反应钩子。

export const getAccessToken = () => {
    const [trigger] = useLazyGetRefreshTokensQuery();
    (...)
    return getUserContext().tokens.find(t => t.type === TokenTypeEnum.ACCESS_TOKEN)
}
Run Code Online (Sandbox Code Playgroud)

我正进入(状态:

React Hook "useLazyGetRefreshTokensQuery" is called in function "getAccessToken" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"
Run Code Online (Sandbox Code Playgroud)

如何刷新 RTK 查询中的令牌?

Fer*_*ted 5

首先,我不知道你是否可以在 API 中使用任何钩子。您可能需要通过以下方式更改获取令牌的方式:

  1. 您是否尝试过使用 重命名您的自定义挂钩useGetAccessToken?错误指出React Hook names must start with the word "use". 这可以解决检索令牌的问题。
  2. 将令牌存储在 cookie 中 - 我最喜欢的方法。
  3. 直接从状态传递令牌作为参数 - 但我从未这样做过。

为了重新获取令牌,

您可以尝试使用文档baseQueryWithReauth中提到的方法,如果返回,则调用您的方法,您可以存储更新的令牌并重试请求。token401refreshToken

在您的实现中它看起来像这样:

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import type {
  BaseQueryFn,
  FetchArgs,
  FetchBaseQueryError,
} from '@reduxjs/toolkit/query'
import { Contacts } from "../datamodel/Contact";
import { getAccessToken } from "../util/userContextManagement";

const baseQuery = fetchBaseQuery({
  baseUrl: '/api/users/current',
  prepareHeaders: (headers) => {
    // this method should retrieve the token without a hook
    const token = getAccessToken();

    if (token) {
      headers.set("authorization", `Bearer ${token}`);
    }
    return headers;
  },
});

const baseQueryWithReauth: BaseQueryFn<
  string | FetchArgs,
  unknown,
  FetchBaseQueryError
> = async (args, api, extraOptions) => {
  let result = await baseQuery(args, api, extraOptions);

  if (result.error && result.error.status === 401) {
    // try to get a new token
    const refreshResult = await baseQuery("/refreshToken", api, extraOptions);

    if (refreshResult.data) {
      // store the new token in the store or wherever you keep it
      api.dispatch(tokenReceived(refreshResult.data));
      // retry the initial query
      result = await baseQuery(args, api, extraOptions);
    } else {
      // refresh failed - do something like redirect to login or show a "retry" button
      api.dispatch(loggedOut());
    }
  }
  return result;
};

export const contactsApi = createApi({
  reducerPath: "contactsApi",
  baseQueryWithReauth,
  endpoints: (builder) => ({
    getContacts: builder.query<Contacts, void>({
      query: () => {
        return {
          url: "/contacts",
          method: "GET",
        };
      },
    }),
  }),
});

export const { useGetContactsQuery } = contactsApi;
Run Code Online (Sandbox Code Playgroud)