在我的应用程序中,我有一个令牌刷新端点
/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 的所有调用。
我想知道是否有一个全局解决方案可以在查询被拒绝时自动调用刷新令牌端点。
嗨,我做了一些与您所做的类似的事情,并且不需要对每个 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();
};
也许您可以使用从错误消息中获得的返回信息来重构它,以更好的方式构建它,或者将其构建为中间件......
注意:此解决方案在获取失败后自动刷新令牌,但不会自动重新获取初始失败的查询。所以还是会造成代码重复。
这就是我到目前为止解决问题的方法:
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
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));
}
}
},
}),
到目前为止,它的效果正如我所希望的那样。我唯一关心的是需要捕获所有突变的错误并再次调用突变。如果有人有任何想法在全球范围内处理它,我会很感兴趣。
这是最终的解决方案,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
action.meta
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
const { endpointName, originalArgs } = rejectedAction;
从商店获取元数据并重新生成初始查询。
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) => ({}),
});