Issue
In my application I have a token refresh endpoint /refresh
which sets new JWT refresh token in a cookie and sends back new JWT access token as json.
Since access token expires after 5 minutes, I need to implement an under the hood refresh logic to kick in whenever access token is expired.
Currently what I am doing is to call the api through RTK Query
and if api rejects the query, I call the refreshTokens
mutation.
And I need to put this logic in all api queries like this:
updateProfile(body)
.unwrap()
.catch((error) => {
if (error && error.status === 403) {
refreshTokens(null); // RTK Query Mutation
updateProfile(body); // RTK Query Mutation
}
});
Doing this seems repetition of code since it needs to be implemented to all the calls to the api.
I wonder if there is a global solution to automatically call the refresh token endpoint when the queries are rejected.
Solution
On my initial solution, I was able to refetch JWTs if access token is expired but I was unable to refetch the initial failed query automatically for all endpoints. So I had to write try-catch blocks to all endpoints. I didnt want to edit the initial answer not to make the solution look complicated, and its also a valid answer.
Here is the final solution where Redux RTK catches authorization error globally and refreshes the JWTs and after refetches the initial failed query automatically.
Same middleware like in my initial answer. But this time refreshTokens
is not a mutation but a query.
// 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);
};
- We retrigger token refresh by
.invalidateTags
method - We get the meta data of the rejected query from
action.meta
and save it to the store to regenerate the failed query after we refresh the tokens.
Then we use onQueryStarted
method of RTK Query to wait for token refresh to complete:
// 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
makes sure the tokens are refreshed.- Get the meta data from the store by
const { endpointName, originalArgs } = rejectedAction;
- And regenerate the inital query.
Thanks to this flow, Redux RTK Query catches all the failed queries, refreshes JWTs and refetches failed queries automatically.
Answered By - damdafayton Answer Checked By - Timothy Miller (PHPFixing Admin)
0 Comments:
Post a Comment
Note: Only a member of this blog may post a comment.