microsoft-authentication-library-for-js: Login popup doesn't close and displays react app itself

Core Library

MSAL.js v2 (@azure/msal-browser)

Core Library Version

2.28.1

Wrapper Library

MSAL React (@azure/msal-react)

Wrapper Library Version

1.4.5

Description

Hi, I think I’ve just found a bug in MSAL.js. I installed the latest version in two different samples for React. When I try to log in, sometimes the popup shows the React Webapp itself and doesn’t close.

  • The first sample I used was this one: https://github.com/Azure-Samples/ms-identity-javascript-react-spa FirstBug
  • And the second one was here in this repository: https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples/msal-react-samples/react-18-sample SecondBug

Error Message

No response

Msal Logs

No response

MSAL Configuration

{
    auth: {
        clientId: "my_client_id",
        authority: "https://login.microsoftonline.com/...",
        redirectUri: "http://localhost:3000"
    },
    cache: {
        cacheLocation: "sessionStorage", 
        storeAuthStateInCookie: false
    },
    system: {	
        loggerOptions: {	
            loggerCallback: (level, message, containsPii) => {	
                if (containsPii) {		
                    return;		
                }		
                switch (level) {		
                    case LogLevel.Error:		
                        console.error(message);		
                        return;		
                    case LogLevel.Info:		
                        console.info(message);		
                        return;		
                    case LogLevel.Verbose:		
                        console.debug(message);		
                        return;		
                    case LogLevel.Warning:		
                        console.warn(message);		
                        return;		
                }	
            }	
        }	
    }
}

Relevant Code Snippets

There is no relevant code snippet as I have only changed the MSAL configuration.

Reproduction Steps

  1. Clone the sample
  2. Install the newest version of @azure/msal-browser and @azure/msal-react through npm.
  3. Adjust MSAL configuration
  4. Run the sample and try to log in several times.

Expected Behavior

Login popup should close, but it doesn’t.

Identity Provider

Azure AD / MSA

Browsers Affected (Select all that apply)

Chrome, Firefox, Edge

Regression

@azure/msal-browser 2.15.0

Source

External (Customer)

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 1
  • Comments: 46 (7 by maintainers)

Most upvoted comments

I think this issue is not resolved yet, even with the blank route. As @hectormmg said, it just mitigate the race condition (?)

I’m having the same issue with my Angular app. Popup shows up, I login, and then the app sign in page is displayed in the popup window which doesn’t close. Console shows this error:

msal.js.browser@2.28.1 : Info - handleRedirectPromise was unable to extract state due to: BrowserAuthError: state_interaction_type_mismatch: Hash contains state but the interaction type does not match the caller.

I’m only using Popup flow, there’s no mention of other interaction types anywhere in my config.

I had the same problem. The above methods didn’t work in my case, so I started digging deeper. I realized that a couple of weeks ago I added the Helmet library for my Express backend. And the problem was there. By default, the Helmet enables Cross Origin Opener Policy with ‘same-origin’ policy: Cross-Origin-Opener-Policy: same-origin

You can read more about it on MDN portal. So, this header doesn’t allow sharing context between the pop-up and main window when origins are different - in that case, the pop-up can’t redirect back to the main window, and code execution continues in the pop-up window.

I have changed Cross Origin Opener Policy to ‘unsafe-none’ policy and now all things are working as expected. Cross-Origin-Opener-Policy: unsafe-none

In my express app it looks like that: app.use( helmet({ ..., crossOriginOpenerPolicy: { policy: 'unsafe-none' }, // Needed for MSAL pop-up }), );

Hope that helps.

I just come up with a workaround

According to tnorling mentioned above, this is a race condition between the popup window and the caller.

if set the redirect uri to blank page not work for you, you can try my workaround

let’s say your redirect uri page is A component, you can save the hash info and try to add back to url in A component

here is an example with Next.js

import { useEffect, useRef } from 'react';
import { useRouter } from 'next/router';

const LoginCallbackPage = () => {
  const router = useRouter();
  const urlHashRef = useRef('');

  useEffect(() => {
    if (window.location.hash) urlHashRef.current = window.location.hash;
  }, []);

  useEffect(() => {
    const handleNavigate = () => {
      if (urlHashRef.current) {
        router.replace(
          {
            pathname: router.pathname,
            hash: urlHashRef.current,
          },
          undefined,
          { shallow: true }
        );
      }
    };

    const intervalTimer = setInterval(handleNavigate, 1000); // need using setInterval to retry otherwise it will not work

    handleNavigate();

    return () => {
      clearInterval(intervalTimer);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [router.pathname]); // router instance will be different after router.replace be called, so I ignore here

  return <div>Loading</div>;
};

this work for me, hope it helps someone

No this is a race condition between the popup window and the caller. The problem only surfaces when the router (or other hash clearing/redirection logic) wins the race. Check if there’s a hash with code= in the address bar of the popup window when this occurs. If there’s not, something inside the popup is removing the hash.

Check this issue, it helped me to come up with a workaround: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/5013

I realized that I had some sort of race condition, here’s how the popup interaction works: your main frame opens a popup and waits for a popup URL to contain a hash (#code…). When it catches the hash, popup is closed and token is being read from the hash. In my case the popup URL was being “eaten” by my app itself (I don’t do anything crazy there, but something obviously was running faster than the hash-expecting main frame), making the main frame msal not able to read the hash.

@nicolastrafenchuk - This is niche. Thank you

Workaround if you are using React and logins with only popups - In index.tsx / index.js => Replace

  ReactDOM.render(
    <React.StrictMode>
      <MsalProvider instance={msalInstance}>
        <App/>
      </MsalProvider>
    </React.StrictMode>,
    document.getElementById('root')
  );

with

if (window.location.hash !== ''){
  console.log("hash found" + window.location.hash);
}
else {
  ReactDOM.render(
    <React.StrictMode>
      <MsalProvider instance={msalInstance}>
        <App/>
      </MsalProvider>
    </React.StrictMode>,
    document.getElementById('root')
  );
}

@sushrut111 This seems to be working in my case but I don’t understand why. Could you please give a thorough explanation for this solution? Thank you!

Workaround if you are using React and logins with only popups - In index.tsx / index.js => Replace

  ReactDOM.render(
    <React.StrictMode>
      <MsalProvider instance={msalInstance}>
        <App/>
      </MsalProvider>
    </React.StrictMode>,
    document.getElementById('root')
  );

with

if (window.location.hash !== ''){
  console.log("hash found" + window.location.hash);
}
else {
  ReactDOM.render(
    <React.StrictMode>
      <MsalProvider instance={msalInstance}>
        <App/>
      </MsalProvider>
    </React.StrictMode>,
    document.getElementById('root')
  );
}

@aNyMoRe0505 In your case there appears to be an interaction type mismatch (indicated on the final log of your screenshot) i.e. you’ve called both loginRedirect and loginPopup and the hash represents one of those operations whereas the temporary cache values in sessionStorage represent the other. In general we recommend picking one interaction type and sticking with it, if that’s not possible for some reason or another you will need to ensure you don’t call both at the same time (the library has protections to prevent this but certain race conditions can still cause this).

This is very likely different from the original issue being discussed here, if you need more help I’d suggest opening your own issue to keep the discussion focused on your scenario.

@henry132109 @Benni03 This happens because something (most likely your router) is removing the response hash from the popup window before the calling page can extract it. Have you tried setting the redirectUri to a static blank page as suggested above? If that’s not possible for your use case the alternative is to ensure that your router doesn’t strip the hash or do any redirection on your redirectUri page, at least when opened in a popup window.