Skip to content

Preventing multiple unauthorized errors #3717

Open
@nm-ivanbasic

Description

@nm-ivanbasic

I have implemented the reauthorization process with RTK Query following the docs:

This is api.ts a single createApi instance in the app:

import { createApi } from '@reduxjs/toolkit/query/react';

import { baseQueryWithReauth } from './utils';

export const api = createApi({
  reducerPath: 'api',
  baseQuery: baseQueryWithReauth,
  endpoints: (builder) => ({}),
});

And this is what i followed, the only thing that differs is

import { fetchBaseQuery } from '@reduxjs/toolkit/query'
import type {
  BaseQueryFn,
  FetchArgs,
  FetchBaseQueryError,
} from '@reduxjs/toolkit/query'
import { tokenReceived, loggedOut } from './authSlice'
import { Mutex } from 'async-mutex'

// create a new mutex
const mutex = new Mutex();
const baseQuery = 
  fetchBaseQuery({
    baseUrl: `${process.env.BASE_URL}`,
    prepareHeaders: (headers: Headers) => generateHeadersIfAny(headers),
  }),

const baseQueryWithReauth: BaseQueryFn<
  string | FetchArgs,
  unknown,
  FetchBaseQueryError
> = async (args, api, extraOptions) => {
  // wait until the mutex is available without locking it
  await mutex.waitForUnlock()
  let result = await baseQuery(args, api, extraOptions)
  if (result.error && result.error.status === 401) {
    // checking whether the mutex is locked
    if (!mutex.isLocked()) {
      const release = await mutex.acquire()
      try {
        const refreshResult = await baseQuery(
          '/get-new-tokens',
          api,
          extraOptions
        )
        if (refreshResult.data) {
          api.dispatch(tokenReceived(refreshResult.data))
          // retry the initial query
          result = await baseQuery(args, api, extraOptions)
        } else {
          api.dispatch(loggedOut())
        }
      } finally {
        // release must be called once the mutex should be released again.
        release()
      }
    } else {
      // wait until the mutex is available without locking it
      await mutex.waitForUnlock()
      result = await baseQuery(args, api, extraOptions)
    }
  }
  return result
}

And this example works perfectly when refresh token did not expire and your refetch route returns 200 and new tokens, but the problem is when your refresh route returns 401 or 400 it goes in the else of if/else but it reruns all the requests that got 401. It's like it only works in the most optimistic case. I would like if my get-new-tokens route returns 400 or 401 don't rerun any of the multiple requests you have in mutex.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions