10

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 { 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;

Suggestions for how to resolve this?

Ravikumar B
  • 779
  • 1
  • 14
  • 25
cjones
  • 8,384
  • 17
  • 81
  • 175

5 Answers5

13

Ok, I was able to sort this out with some help:

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;
cjones
  • 8,384
  • 17
  • 81
  • 175
  • 1
    This actually dint work for me fully, this code is calling LoginRedirect call twice. Hence if I write some API call after login that is being cancelled first time executed after second login – Ravikumar B Jul 26 '21 at 18:31
8

You can use simply MsalAuthenticationTemplate component instead of AuthenticatedTemplate/UnauthenticatedTemplate:

import React from "react";
import { MsalAuthenticationTemplate } from "@azure/msal-react";
import { InteractionType } from "@azure/msal-browser";

function ErrorComponent({error}) {
    return <p>An Error Occurred: {error}</p>;
}

function LoadingComponent() {
    return <p>Authentication in progress...</p>;
}

export function Example() {
    const authRequest = {
        scopes: ["openid", "profile"]
    };

    return (
        // authenticationRequest, errorComponent and loadingComponent props are optional
        <MsalAuthenticationTemplate 
            interactionType={InteractionType.Popup} 
            errorComponent={ErrorComponent} 
            loadingComponent={LoadingComponent}
        >
            <p>At least one account is signed in!</p>
        </MsalAuthenticationTemplate>
      )
};
Simone
  • 2,304
  • 6
  • 30
  • 79
  • 1
    I don't believe this is fully correct. The same repo you got this from also links to a folder with multiple examples of how to use `MsalAuthenticationTemplate` ([e.g. this one](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/samples/msal-react-samples/react-router-sample/src/pages/Profile.jsx)). And in every example, components wrapped by the MsalAuthenticationTemplate _still_ have to check the Interaction status to ensure that login has actually fully completed. – user3781737 Feb 09 '23 at 22:16
3

A better solution is to put an MsalAuthenticationTemplate in the Router in App.jsx like so:

return (
    <Router>
      <MsalProvider instance={instance}>
        <MsalAuthenticationTemplate
          interactionType={InteractionType.Redirect}
          authenticationRequest={authRequest}
          errorComponent={ErrorComponent}
          loadingComponent={Loading}
          >

This has the effect of causing a redirect to sign-in page when trying to access any route within the MsalAuthenticationTemplate.

jdempcy
  • 136
  • 1
  • 4
2

As per @cjones solution I tried several approaches tweaking the solution a bit to get a better version suitable for me and posting the same here. This would help if someone has same issue.

There are 2 issues with his solution.

  1. LoginRedirect was being called twice.
  2. If you have any API calls to be made after authentication success, that would get cancelled first because of the second call for LoginRedirect. It executes after second LoginRedirect call(Though, this second login attempt will not ask for credentials, but it does the refreshing of page.

I tried to solve this problem with following approach.

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

    if (inProgress === InteractionStatus.None && !isAuthenticated) {
        setTimeout(() => {
            if (accounts.length === 0) {
                instance.loginRedirect();
            }
        }, 500)
    }
 
 /** Rest of the code here **/

}
Ravikumar B
  • 779
  • 1
  • 14
  • 25
1

Here is a simpler way. Use useMsalAuthentication directly given by @azure/msal-react.

import { AuthenticatedTemplate, UnauthenticatedTemplate, useMsalAuthentication } from '@azure/msal-react';
import { InteractionType } from '@azure/msal-browser';

const App = () => {
  const { login, result, error } = useMsalAuthentication(InteractionType.Silent, loginRequest);

  if (error) {
    // handle error...
  }

  if (result) {
    // handle result...
  }

  return (
    <div className="App">
      <AuthenticatedTemplate>
        <div>Signed in as {login}...</div>
      </AuthenticatedTemplate>

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

export default App;

This will redirect only if needed, you do not need to implement custom redirection here.

Fire Druid
  • 246
  • 2
  • 9