MSAL.js and Axios - aquireTokenPopup interaction in progress

121 views Asked by At

We're using axios request interceptors to aquire access token from MSAL.js and add it as a bearer token to all API requests to our backend:

const acquireToken = async () => {
  const accounts = msalInstance.getAllAccounts();

  if (accounts.length === 0) return null;

  const request = {
    scopes: process.env.scopes || [],
    account: accounts[0],
  };

  try {
    return await msalInstance.acquireTokenSilent(request);
  } catch (error) {
    if (error instanceof InteractionRequiredAuthError) {
      return await msalInstance.acquireTokenPopup(request);
    }

    return null;
  }
}

const getAccessToken = async () => {
  const token = await acquireToken();
  return token?.accessToken || '';
}

axiosInstance.interceptors.request.use(
  async (config) => {
    const configured = config;
    const token = await getAccessToken();
    configured.headers.Authorization = `Bearer ${token}`;
    return configured;
  },
  (error: any) => Promise.reject(error)
);

When a user opens a certain route within the app, multiple requests are sent simultaneously, something like this:

useEffect(() => {
  Promise.all([getMovies(), getActors()]).then(([_movies, _actors]) => {
    // ...
  });
}, []);

We don't really want to make calls sequentially as some of them may be slow and running them in parallel makes it more efficient.

The problem is that whenever all tokens expire and interactive login is required, all parallel requests are falling under aquireTokenPopup and only one of them is working and the other is failing with interaction_in_progress error.

MSAL documentation says that aquireTokenSilent returns currently processing promise if parallel requests are made - any reason why this isn't the case for aquireTokenPopup?

https://learn.microsoft.com/en-us/javascript/api/@azure/msal-browser/publicclientapplication?view=msal-js-latest#method-details https://learn.microsoft.com/en-us/javascript/api/@azure/msal-browser/publicclientapplication?view=msal-js-latest#method-details

And what is the best way to handle this situation? Is there some library out there that wraps this MSAL calls and only launches one call if multiple calls are made at the same time, if MSAL itself isn't doing it? Or am I missing something and calling MSAL from within the axios request interceptors isn't a good idea at all?

1

There are 1 answers

0
andkorsh On

It looks like this simple addition fixes the problem, but I'm still wondering why MSAL doesn't do the same and whether this is an appropriate solution.

let currentTokenPromise: Promise<string> | null = null;

const getAccessToken = (scopes: string[]): Promise<string> => {
  if (currentTokenPromise !== null) {
    return currentTokenPromise;
  }

  // if a request came for the same scopes, return the existing promise.
  // todo: if a request came for different scopes, wait for the current to complete and then execute new promise.

  const tokenPromise = acquireToken(scopes).then((t) => {
    currentTokenPromise = null;
    return t?.accessToken || '';
  });

  currentTokenPromise = tokenPromise;

  return tokenPromise;
};