How to get MSAL access token on every API call in React app

mdodge 21 Reputation points
2024-08-06T15:27:11.0366667+00:00

I'm trying to figure out how to structure my React app to get the MSAL access token on every api call. I am either running into circular call errors or uninitialized_public_client_application: You must call and await the initialize function before attempting to call any other MSAL API.

First I tried to export my msal instance from my main.tsx file to use later in my api functions on every api call. That kept throwing these circular errors like this Vite error: [hmr] /src/components/App.tsx failed to apply HMR as it's within a circular import. Reloading page to reset the execution order. To debug and break the circular import, you can run vite --debug hmr to log the circular dependency path if a file change triggered it.

Or this React error: Warning: You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it.

Then I tried not exporting it and instead following this example from Microsoft: acquiring-an-access-token-outside-of-a-react-component

Then that caused this error: uninitialized_public_client_application: You must call and await the initialize function before attempting to call any other MSAL API. For more visit: aka.ms/msaljs/browser-errors

So I'm stumped. Any help would be appreciated.

Azure App Service
Azure App Service
Azure App Service is a service used to create and deploy scalable, mission-critical web apps.
7,663 questions
{count} votes

1 answer

Sort by: Most helpful
  1. mdodge 21 Reputation points
    2024-08-06T17:17:40+00:00

    I'm so confused, after playing with my code again, I think I got it working now. And I have no idea what I did differently.

    Anyways, here is my working api functions file:

    import axios from 'axios';
    import {msalConfig} from "../authConfig";
    import {msalInstance} from '../main';
    
    const baseURL = import.meta.env.VITE_BASE_URL;
    
    const veryShortTimeout = 5000;
    const shortTimeout = 20000;
    const medTimeout = 30000;
    const longTimeout = 60000;
    
    const delayAPICall: boolean = false;
    //const delayAPICall = true;
    const secondsDelay = 2;
    
    
    const axiosInstance = axios.create({
        baseURL: baseURL + '/',
        delayed: delayAPICall
    });
    
    
    async function getAccessToken(): Promise<string | null> {
        const activeAccount = msalInstance.getActiveAccount(); // This will only return a non-null value if you have logic somewhere else that calls the setActiveAccount API
        const accounts = msalInstance.getAllAccounts();
    
        console.log({accounts});
        if (!activeAccount && accounts.length === 0) {
           /*
           * User is not signed in. Throw error or wait for user to login.
           * Do not attempt to log a user in outside of the context of MsalProvider
           */
        }
        if (activeAccount || accounts.length > 0) {
           const currentAccount = accounts[0];
           const accessTokenRequest = {
              scopes: [import.meta.env.VITE_QUOTES_API_SCOPE],
              account: activeAccount || accounts[0],
           };
    
           if (currentAccount && currentAccount.tenantId == msalConfig.auth.tenantId) {
              const roles = (currentAccount.idTokenClaims as { [key: string]: any }).roles;
              console.log({roles});
              if (roles) {
                 //     const intersection = Object.keys(appRoles).filter((role) => roles.includes(role));
                 //     if (intersection.length > 0) {
                 try {
                    const accessTokenResponse = await msalInstance.acquireTokenSilent(accessTokenRequest);
                    return `Bearer ${accessTokenResponse.accessToken}`;
                 } catch (e) {
                    console.log({e});
                    return null;
                 }
              }
           }
           return null;
        }
    
        return null;
    }
    
    // let result: { accessToken: string }, msalError: string;
    axiosInstance.interceptors.request.use(async (config) => {
        config.headers['request-startTime'] = new Date().getTime();
    
        // 07-28-24 - new token code, we can even check the url path if we need to
        // if (config.url.indexOf("/admin") !== 0) {
        // 8-2-24 - uncomment this next line back in after circular issue is revolved
        config.headers.Authorization = await getAccessToken();
        // }
        // return config;
    
    
        if (config.delayed) {
           // adding delay for now to show loading of resources...
           return new Promise((resolve) =>
              setTimeout(() => resolve(config), 1000 * secondsDelay)
           );
        }
    
        return config;
    });
    
    axiosInstance.interceptors.response.use((response) => {
        const currentTime = new Date().getTime();
        const startTime = response.config.headers['request-startTime'];
        response.headers['request-duration'] = currentTime - startTime;
        return response;
    });
    
    export default axiosInstance;
    
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.