AzureAD / microsoft-authentication-library-for-js

Microsoft Authentication Library (MSAL) for JS
http://aka.ms/aadv2
MIT License
3.64k stars 2.65k forks source link

useIsAuthenticated() always evaluating to false and redirecting when already logged in #3250

Closed cheslijones closed 3 years ago

cheslijones commented 3 years ago

Library

Description

I'm trying to adapt the sample project for my needs.

My needs are essentially:

  1. Automatically redirect login if the user is not authenticated when they navigate to the root route...
  2. If they are, load the protected child components.

I have step 1. working as expected. However, after they have been signed in it seems like it is trying to reroute again and I get:

interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API. For more visit: aka.ms/msaljs/browser-errors

70 | 
71 | useEffect(() => {
72 |   if (!isAuthenticated) {
73 |     msalInstance.loginRedirect(loginRequest);
| ^  74 |   }
75 | })
76 | 

It does this whether or not there is the !isAuthenticated conditional.

The usage of the useIsAuthenticated comes from this documentation and appears to evaluate to false even if the user is logged in already.

This is what I have thus far:

import React, { useState, useEffect } from 'react';
import {
  MsalProvider,
  AuthenticatedTemplate,
  UnauthenticatedTemplate,
  useMsal,
  useAccount,
  useIsAuthenticated
} from '@azure/msal-react';
import { PublicClientApplication } from '@azure/msal-browser';
import { msalConfig, loginRequest } from './authConfig';
import { ProfileData, callMsGraph } from './graph';
import Button from 'react-bootstrap/Button';

const ProfileContent = () => {
  const { instance, accounts } = useMsal();
  const account = useAccount(accounts[0] || {});
  const [graphData, setGraphData] = useState(null);

  function RequestProfileData() {
    if (account) {
      instance
        .acquireTokenSilent({
          ...loginRequest,
          account: account,
        })
        .then((response) => {
          callMsGraph(response.accessToken).then((response) =>
            setGraphData(response)
          );
        });
    }
  }

  return (
    <>
      <h5 className='card-title'>
        Welcome {account ? account.name : 'unknown'}
      </h5>
      {graphData ? (
        <ProfileData graphData={graphData} />
      ) : (
        <Button variant='secondary' onClick={RequestProfileData}>
          Request Profile Information Test
        </Button>
      )}
    </>
  );
};

const MainContent = () => {
  return (
    <div className='App'>
      <AuthenticatedTemplate>
        <ProfileContent />
      </AuthenticatedTemplate>

      <UnauthenticatedTemplate>
        <div>Signing in...</div>
      </UnauthenticatedTemplate>
    </div>
  );
};

const App = () => {
  const msalInstance = new PublicClientApplication(msalConfig);
  const isAuthenticated = useIsAuthenticated();

  useEffect(() => {
    if (!isAuthenticated) {
      msalInstance.loginRedirect(loginRequest);
    }
  })

  return (
    <MsalProvider instance={msalInstance}>

      <MainContent />
    </MsalProvider>
  );
};

export default App;

Suggestions for how to resolve this?

Source

tnorling commented 3 years ago

@cheslijones You need to either move your useIsAuthenticated and loginRedirect calls into your MainContent component or you need to move the MsalProvider up one level to wrap your App component. The provider needs to be rendered above any and all msal hook and component calls.

Also I would recommend instantiating PublicClientApplication outside of your component tree as you may run into some issues if this is reinstantiated due to a rerender.

cheslijones commented 3 years ago

@tnorling Thanks, make sense, but I appear to be running into the same issue where useIsAuthenticated is always evaluating to false and causing a loop.

I've refactored with your suggestion and to make things cleaner:

import { Configuration, RedirectRequest } from '@azure/msal-browser';

// Config object to be passed to Msal on creation
export const msalConfig: Configuration = {
  auth: {
    clientId: '<client_id>',
    authority: 'https://login.microsoftonline.com/<tenant_id>',
  },
};

// Add here scopes for id token to be used at MS Identity Platform endpoints.
export const loginRequest: RedirectRequest = {
  scopes: ['User.Read'],
};

// Add here the endpoints for MS Graph API services you would like to use.
export const graphConfig = {
  graphMeEndpoint: 'https://graph.microsoft.com/v1.0/me',
};
// index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import { PublicClientApplication } from '@azure/msal-browser';
import { msalConfig } from './components/authConfig';
import { MsalProvider } from '@azure/msal-react';

const msalInstance = new PublicClientApplication(msalConfig);

ReactDOM.render(
  <React.StrictMode>
    <MsalProvider instance={msalInstance}>
      <App />
    </MsalProvider>
  </React.StrictMode>,
  document.getElementById('root')
);
// App.tsx
import React, { useEffect } from 'react';
import {
  AuthenticatedTemplate,
  UnauthenticatedTemplate,
  useMsal,
  useIsAuthenticated,
} from '@azure/msal-react';
import { loginRequest } from './authConfig';

const App = () => {
  const isAuthenticated = useIsAuthenticated();
  const { instance } = useMsal();

  console.log(isAuthenticated);

  useEffect(() => {
    if (!isAuthenticated) {
      instance.loginRedirect(loginRequest);
    }
  });

  return (
    <div className='App'>
      <AuthenticatedTemplate>
        <div>Signed in...</div>
      </AuthenticatedTemplate>

      <UnauthenticatedTemplate>
        <div>Signing in...</div>
      </UnauthenticatedTemplate>
    </div>
  );
};

export default App;
tnorling commented 3 years ago

@cheslijones Please also check that inProgress === InteractionStatus.None before calling loginRedirect. You can get the inProgress value from useMsal hook.

Alternatively you can use the useMsalAuthentication hook which does all of this for you. You can read more about that hook here

cheslijones commented 3 years ago

That did it, thanks for the help with such a rudimentary thing:

const App = () => {
  const isAuthenticated = useIsAuthenticated();
  const { instance, inProgress } = useMsal();

  if (inProgress === InteractionStatus.None && !isAuthenticated) {
    instance.loginRedirect(loginRequest);
  }

  return (
    <div className='App'>
      <AuthenticatedTemplate>
        <div>Signed in...</div>
      </AuthenticatedTemplate>

      <UnauthenticatedTemplate>
        <div>Signing in...</div>
      </UnauthenticatedTemplate>
    </div>
  );
};

export default App;