1

I am trying to create Redux middleware which fetch token (lately will check if token is expired but for testing purpose I now only fetch token). Every time RTK-Query starts its action. My issue is that when I am fetching/getting token from Azure it is async and it took a while to get the token, but my middleware just continue without waiting to my token and I don't get token, not in correct moment before RTK-Query data fetching. Maybe I don't really understand asynchronous logic in Redux and middleware,.

authMiddleware.ts:

export const authMiddleware: Middleware =
  ({ dispatch, getState }) => next => async (action: PayloadAction) => {
    // const store = getState() as RootState;
    console.log('Dispatching action:', action.type);
    logWithTime('Middleware: action is a function and started');

    if (action.type === 'apiInvoices/config/middlewareRegistered') {
      logWithTime(
        'middleware in action: apiInvoices/config/middlewareRegistered'
      );
      let token: string | void = '';
      console.log('Action in if: ', action.type)
      const tokenProvider: AadTokenProvider = await getAadTokenProvider();

      if (tokenProvider) {
        logWithTime('Token provider set');
      }

      // check if tokenProvider is not null
      if (tokenProvider) {
        console.log('Token provider:', tokenProvider);
      } else {
        console.log('Token provider is null')
      }

      // fetch token, wait till token is fetched and then set
      // token in store then return next action
      token = await tokenProvider.getToken(
        '6cbc9b1e-901d-4a99-a947-ae36ffe3ac38'
      )
        .then(token => {
          setToken({ endpoint: 'getInvoiceByID', token: token })
        })
        .then(() => {
          logWithTime('Token fetched and set in store');
          next(action)
        });
    }
  };

store.ts:

import { configureStore } from '@reduxjs/toolkit';
import { tokenReducer } from '../slices/tokenSlice';
import { apiInvoices } from '../api/invoiceApi';
import logger from 'redux-logger';
import { authMiddleware } from '../middleware/authMiddleware/authMiddleware';

const store = configureStore({
  reducer: {
    token: tokenReducer,
    [apiInvoices.reducerPath]: apiInvoices.reducer,
    // other reducers
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({})
      .concat(apiInvoices.middleware, authMiddleware, logger),
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export default store

Here is some console outputs where I can that I got token too late:

Dispatching action: apiInvoices/config/middlewareRegistered authMiddleware.ts:15 [2024-11-14T08:06:58.937Z] Middleware: action is a function and started

authMiddleware.ts:15 [2024-11-14T08:06:58.937Z] middleware in action: apiInvoices/config/middlewareRegistered

authMiddleware.ts:25 Action in if: apiInvoices/config/middlewareRegistered

authMiddleware.ts:15 [2024-11-14T08:06:58.940Z] Token provider set

authMiddleware.ts:33 Token provider: e {_aadConfiguration: {…},
_oboConfiguration: {…}, _tokenAcquisitionEvent: e, onBeforeRedirectEvent: e, popupEvent: e, …}

authMiddleware.ts:19 Dispatching action: apiInvoices/executeQuery/pending

authMiddleware.ts:15 [2024-11-14T08:06:58.944Z] Middleware: action is a function and started

authMiddleware.ts:15 [2024-11-14T08:06:58.944Z] Middleware: action is a function and ended

authMiddleware.ts:42 action apiInvoices/executeQuery/pending @ 09:06:58.944

2 Answers 2

1

It's a bit of a Javascript anti-pattern to use async/await and Promise chains. The other potential issue I see is that your middleware doesn't return the result of calling next(action) for the next middleware.

By using async/await you can flatten the nested logic you've bundled up in the getToken Promise chain.

Suggestion:

  • Only run the token provider and token fetching logic if the action.type matches
  • Only run the token fetching logic if a token provider is available
  • await and assign the result of tokenProvider.getToken to token
  • Always unconditionally return the result of calling next(action)
export const authMiddleware: Middleware = store => next => async (action) => {
  console.log('Dispatching action:', action.type);
  logWithTime('Middleware: action is a function and started');

  if (action.type === 'apiInvoices/config/middlewareRegistered') {
    logWithTime(
      'middleware in action: apiInvoices/config/middlewareRegistered'
    );
      
    console.log('Action in if: ', action.type)
    const tokenProvider: AadTokenProvider = await getAadTokenProvider();

    // check if tokenProvider is not null
    if (tokenProvider) {
      console.log('Token provider:', tokenProvider);
      logWithTime('Token provider set');

      // fetch token, wait till token is fetched and then set
      // token in store then return next action
      const token = await tokenProvider.getToken(
        '6cbc9b1e-901d-4a99-a947-ae36ffe3ac38'
      );

      setToken({ endpoint: 'getInvoiceByID', token: token });
      logWithTime('Token fetched and set in store');
    } else {
      console.log('Token provider is null')
    }
  }

  return next(action);
};
3
  • Hello, first of all, thank you for your advice. You are right, i should just use async, await or promise. I choose async and await. I made changes to my code which you suggest, but still my rtk query make request before authMiddleware get token and store it in redux store. Is there any best practice to getting token for authentication calls to api ? One of approches that bump into is use timeout a check if token is alredy stored in store, but i dont like timeout i would like to have better approach, thank you. Commented Nov 15, 2024 at 11:19
  • @JakubHusařík You might play around with the middleware order between apiInvoices.middleware and authMiddleware so one processes actions before the other. If you need more help then see if you can create a running CodeSandbox demo that reproduces the Redux API slice and middleware issue that we can inspect live.
    – Drew Reese
    Commented Nov 16, 2024 at 1:10
  • @JakubHusařík Another common pattern for token fetching/etc is to do this in a custom basequery that checks if a token exists and running some asynchronous logic prior to completing the query request.
    – Drew Reese
    Commented Nov 16, 2024 at 1:14
0

I solve my issue using custom base query which check if there is token, if not, it will fetch token first, then customBaseQuery continue to fetch data. @Drew Reese Thank you very much for you help and hints.

1
  • Hello, you should validate the answer of your question into the topic, and avoid using answer post to comment on the answer (I know that for now you cannot do otherwise due to your current points but just for further notice). Have a good one!
    – OOM
    Commented Nov 20, 2024 at 9:47

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.