3

I'm trying to create a react SPA, using AzureAD/MSAL for authentication. I have a Landing page at / with a Login button and a separate app Home page on a /home route. If someone tries to navigate directly to /home without authenticating, I want to redirect them to the Landing page.

I do this with:

  const navigate = useNavigate();
  const { instance, inProgress } = useMsal();
  const isAuthenticated = useIsAuthenticated();

  useEffect(() => {
    if (inProgress === InteractionStatus.None && !isAuthenticated) {
      navigate('/');
    }
  }, [inProgress, isAuthenticated, navigate]);

Which seems to work as intended when navigating directly to /home.

The Issue I have is when the user first goes to the Landing page and logs in, it goes to /home then this redirect still kicks in and takes the user back to the Landing page. On clicking the Login button again, it does go to /home successfully. Is this because the first time /home is loaded before authentication is fully complete? How do I wait for auth to complete?

Code:

import React, { useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route, Link, useNavigate } from 'react-router-dom';
import { MsalProvider, useMsal, useIsAuthenticated } from '@azure/msal-react';
import { InteractionStatus } from '@azure/msal-browser';
import ReactDOM from 'react-dom/client';
import { PublicClientApplication } from '@azure/msal-browser';
import { msalConfig } from './js/authConfig';

const LandingPage = () => {
  const { instance } = useMsal();

  const handleLogin = () => {
    instance.loginRedirect();
  };

  return (
    <div>
      <h1>Landing Page</h1>
      <button onClick={handleLogin}>Login</button>
    </div>
  );
};

const HomePage = () => {
  const navigate = useNavigate();
  const { instance, inProgress } = useMsal();
  const isAuthenticated = useIsAuthenticated();

  useEffect(() => {
    if (inProgress === InteractionStatus.None && !isAuthenticated) {
      navigate('/');
    }
  }, [inProgress, isAuthenticated, navigate]);

  return (
    <div>
      <h1>Home Page</h1>
    </div>
  );
};

const msalInstance = new PublicClientApplication(msalConfig);

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <MsalProvider instance={msalInstance}>
      <Router>
        <Routes>
          <Route path="/" element={<LandingPage />} />
          <Route path="/home" element={<HomePage />} />
        </Routes>
      </Router>
    </MsalProvider>
  </React.StrictMode>
);

My msalConfig has

  redirectUri: "/home",
  navigateToLoginRequestUrl: false,
RaGe
  • 22,696
  • 11
  • 72
  • 104

3 Answers3

3

From what I can find it seems that this is the correct way to handle this, when the HomePage component mounts, the authentification process should be either in progress or already finished and the user is authenticated unless there is a moment where both are false juste before isAuthenticated passes to true.

what about navigate to the HomePage only when the user is successfully authenticated?
just leave the HomePage as it is and on the landing page, when you click the login button, you just login without redirect and set isAuthCompleted state to true then when isAuthenticated is true you navigate to the home page, it seems more logical, redirecting the user to the homepage only when he finishes the authentication process:

const navigate = useNavigate();
const isAuthenticated = useIsAuthenticated();

const [isAuthCompleted, setIsAuthCompleted] = useState(false);

useEffect(() => {
  if (isAuthCompleted && isAuthenticated) {
    navigate("/home");
  }
}, [isAuthCompleted, isAuthenticated]);

return (
  <>
    <button
      onClick={() => {
        //... authentificate with Azure
        setIsAuthCompleted(true);
      }}
    >
      login
    </button>
  </>
)

this should solve the issue.

Ahmed Sbai
  • 10,695
  • 9
  • 19
  • 38
  • I ended up going with something very similar to your 2nd suggestion. Navigate inside useEffect on the landing page when auth is completed. And a similar Navigate to landing page inside /home to take unauthed users or after logout, back to the landing page. – RaGe Jun 30 '23 at 12:21
  • thats the point, with the first suggestion I wanted to give more time to isAuthenticated to become true but if it does not work so it was not enough,so I deleted it, the second one makes more sens to me, and for `inProgress` it is usually used to show animation and you want do this in the landing page so you can still do that, you don't need it in the homePage anyway – Ahmed Sbai Jun 30 '23 at 21:09
0

try with setting loading state

const HomePage = () => {
  const navigate = useNavigate();
  const { instance, inProgress } = useMsal();
  const isAuthenticated = useIsAuthenticated();
const [isLoading, setIsLoading] = useState(true);
  useEffect(() => {
    if (inProgress === InteractionStatus.None && !isAuthenticated) {
      navigate('/');
    }
else{
setIsLoading(false)
}
  }, [inProgress, isAuthenticated, navigate]);

  return (
<>
{
!loading && ( <div>
      <h1>Home Page</h1>
    </div>)
}
</>
  );
};

const msalInstance = new PublicClientApplication(msalConfig);

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <MsalProvider instance={msalInstance}>
      <Router>
        <Routes>
          <Route path="/" element={<LandingPage />} />
          <Route path="/home" element={<HomePage />} />
        </Routes>
      </Router>
    </MsalProvider>
  </React.StrictMode>
);
Sweety SK
  • 351
  • 1
  • 10
0

In the following code you may do like this:

        const navigate = useNavigate();
      const { instance, inProgress } = useMsal();
      const isAuthenticated = useIsAuthenticated();

      useEffect(() => {
        if (isAuthenticated) {
          navigate('/home');
        }else{
         navigate('/');
        }
      }, [inProgress, isAuthenticated, navigate]);

This may help you.