Problem with Invalid hook call for react-msal with Axios

Viewed 472

I'm using react-msal to my application. I need to acquire the access token and attach it to the axios globally, but unfortunately, they only provide hooks to get the access token (as far as I know).

So far, here's my api.js file.

import axios from "axios";
import { useMsal } from "@azure/msal-react";
const axiosInstance = axios.create({
  baseURL: "https://localhost:4211/api",
});

const { instance, accounts } = useMsal();

instance
  .acquireTokenSilent({
    ...loginApiRequest,
    account: accounts[0],
  })
  .then((response) => {
    axiosInstance.defaults.headers.common[
      "Authorization"
    ] = `Bearer ${response.accessToken}`;
  })
  .catch((error) => {
    console("Error acquiring access token");
  });

export default axiosInstance;

And here's I call my API in my component.

api.get('/foods').then(response => {
    alert(response.data)
}).catch(error => {
    console.log(error.response)
})

But I'm getting an issue that says: Error: Invalid hook call. Hooks can only be called inside of the body of a function component. which is obvious but I need alternatives to get the access token and assign it to my axios globally as part of the header so I don't need to rewrite header each time I need to call an endpoints. Any help?

2 Answers

This is a React application, right?

You can't call hooks from outside of your React components, or other hooks.

https://reactjs.org/docs/hooks-rules.html

enter image description here

You could do something like this:

const App = () => {
  const { instance, accounts } = useMsal();

  useEffect(() => {
    instance.acquireTokenSilent()
    .then(() => {})
    .catch(() => {})
  },[]);
};

You can use PublicClientApplication instance passed into the MsalProvider.

To get the accounts call instance.getAllAccounts().

You can't access the inProgress value outside of a component or context, but since you're just using acquireTokenSilent you probably will not need it.

below is my working sample.

import axios from 'axios';
import * as App from '../index'
import * as utils from './utils'

const instance = axios.create({
    baseURL: utils.getEndpoint(),
    timeout: 15000
});

instance.interceptors.request.use(function (config) {

    const instance = App.msalInstance;
    const accounts = instance.getAllAccounts();

    const accessTokenRequest = {
        scopes: ["user.read"],
        account: accounts[0],
    };

    return instance
        .acquireTokenSilent(accessTokenRequest)
        .then((accessTokenResponse) => {
            // Acquire token silent success
            let accessToken = accessTokenResponse.accessToken;
            // Call your API with token
            config.headers.Authorization = `Bearer ${accessToken}`;
            return Promise.resolve(config)
        })
}, function (error) {
    return Promise.reject(error);
});

instance.interceptors.response.use((response) => {
    if(response.status === 401) {
        // Clear local storage, redirect back to login
        window.location.href = "/logout"
    }
    return response;
}, (error) => {
    return Promise.reject(error);
});

export default instance

and index.js below

import React from "react";
import ReactDOM from "react-dom";

import { PublicClientApplication, EventType } from "@azure/msal-browser";
import { msalConfig } from "./authConfig";

import App from "./App";
import * as serviceWorker from "./serviceWorker";

export const msalInstance = new PublicClientApplication(msalConfig());

// Default to using the first account if no account is active on page load
if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) {
  // Account selection logic is app dependent. Adjust as needed for different use cases.
  msalInstance.setActiveAccount(msalInstance.getAllAccounts()[0]);
}

// Optional - This will update account state if a user signs in from another tab or window
msalInstance.enableAccountStorageEvents();

msalInstance.addEventCallback((event) => {
  if (event.eventType === EventType.LOGIN_SUCCESS && event.payload.account) {
    const account = event.payload.account;
    msalInstance.setActiveAccount(account);
  }
});

ReactDOM.render(<App pca={msalInstance} />,
  document.getElementById("app"),
);

serviceWorker.unregister();

Related