Skip to content

Preventing multiple unauthorized errors #3717

@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.

Activity

smyja

smyja commented on Nov 5, 2023

@smyja

did you resolve this?

herarya

herarya commented on Nov 6, 2023

@herarya

same issue here, follow the doc, after refresh token, need to set to redux first and continue the api call

allybee

allybee commented on Mar 19, 2024

@allybee

It seems to work as documented with async-mutex@v0.4.1, but is broken in async-mutex@v0.5.0.

AlexeyEsin

AlexeyEsin commented on Aug 21, 2024

@AlexeyEsin

I had the same problem, and I solved it by adding a query restart condition. Since I store the authorization status in the redux slice, I can set it to false in the loggedOut function and restart the request only if the status is set to true. Here's what it looks like:

// wait until the mutex is available without locking it
await mutex.waitForUnlock();
if ((api.getState() as RootState).authState.isAuthorized) {
  result = await baseQuery(args, api, extraOptions);
}

Also, if you store the refresh token in localStorage, you can clear it in loggedOut and check for its presence when restarting the query.

It might be worth adding this to the documentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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

        Participants

        @markerikson@allybee@herarya@smyja@AlexeyEsin

        Issue actions

          Preventing multiple unauthorized errors · Issue #3717 · reduxjs/redux-toolkit