AzureAD / microsoft-authentication-library-for-js

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

Hashchange seemingly not getting handled in auth code flow, and thus no access token is requested (Safari) #4458

Closed mikkelmk closed 1 year ago

mikkelmk commented 2 years ago

Core Library

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

Core Library Version

2.21.0

Wrapper Library

MSAL React (@azure/msal-react)

Wrapper Library Version

1.2.0

Description

Somehow the seamless SSO in Safari does not perform an actual redirect for the auth code flow when using InteractionType.Redirect, but seemingly just adds the hash code directly to the URL.

(I'm not familiar with how seamless SSO works or any specifics of our seamless SSO configuration, so i cannot provide further details related to that.)

msal-browser/msal-react does not react to this hash change, and thus the user is left hanging with #code=XXX in the URL without requesting an access token.

The hacky fix below either reloads the page or creates a new instance when a hashchange containing "#code" is detected. That this works confirms that it is a problem of msal-browser/msal-react not reacting to an auth code provided this way.

Error Message

None

Msal Logs

No response

MSAL Configuration

const msalConfig: Configuration = {
  auth: {
    clientId: config.clientId,
    authority: "https://login.microsoftonline.com/" + config.tenantId,
    redirectUri: window.location.origin,
  },
  cache: {
    cacheLocation: "localStorage",
    storeAuthStateInCookie: false,
  },
};

Relevant Code Snippets

// Including this serves as a temporary fix: 

window.addEventListener('hashchange', () => {
  if (window.location.hash.startsWith('#code')) window.location.reload();
});

// The library is used in the following way:

<MsalProvider instance={publicClientApplication}>
  <MsalAuthenticationTemplate
    interactionType={InteractionType.Redirect}
    authenticationRequest={authRequest}
    loadingComponent={LoginLoading}
    errorComponent={LoginError}
  >
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </MsalAuthenticationTemplate>
</MsalProvider>

// It's also possible to fix it without reloading by creating 
// a new PublicClientApplication instance like so:

const HackyFix = () => {
  const getNewInstance = () =>
    // Our own custom function
    getMsalPublicClientApplication({
      clientId: environment.CLIENT_ID,
      tenantId: environment.TENANT_ID,
    });

  const [instance, setInstance] = useState(getNewInstance);

  useEffect(() => {
    window.addEventListener('hashchange', () => {
      if (window.location.hash.startsWith('#code')) setInstance(getNewInstance);
    });
  }, []);

  return (
    <MsalProvider instance={instance}>
      <MsalAuthenticationTemplate
        interactionType={InteractionType.Redirect}
        authenticationRequest={authRequest}
        loadingComponent={LoginLoading}
        errorComponent={LoginError}
      >
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </MsalAuthenticationTemplate>
    </MsalProvider>
  );
};

Reproduction Steps

  1. I assume using seamless SSO is needed to reproduce the bug
  2. Clear localStorage or use a private window
  3. Access site in Safari
  4. Be left hanging with #code=XXX in the URL :(

Expected Behavior

The library should request an access token when provided an auth code in the URL

Identity Provider

Azure AD / MSA

Browsers Affected (Select all that apply)

Safari

Regression

No response

Source

External (Customer)

jasonnutter commented 2 years ago

@mikkelmk I believe we have heard about this in #4008 (see this comment). Does this appear like the same issue?

And also curious if manually invoking handleRedirectPromise works as described here.

mikkelmk commented 2 years ago

Yes, that seems to be the exact same issue, also with no requests in the network tab. tcpdump shows activity however, so macOS must be doing some magic in the background.

Manually invoking handleRedirectPromise as below seems to work perfectly:

window.addEventListener('hashchange', () => {
  if (window.location.hash.startsWith('#code')) {
    publicClientApplication.handleRedirectPromise(window.location.hash);
  }
});

Notice that window.location.hash must be given as parameter even though the doc states it "defaults to the current value of window.location.hash"

MagnusHJensen commented 2 years ago

Closed #4444 since it's the same issue.

As far as we can see, it's fixed natively with Safari 15.2, not sure about older versions than 15.1

mikkelmk commented 2 years ago

I have the issue on version 15.2 (17612.3.6.1.6) of Safari

jasonnutter commented 2 years ago

@mikkelmk @MagnusHJensen Thank you for the information, I'll look to see if we can properly handle this in the library, thanks!

sameerag commented 2 years ago

Can you please test this on the latest version of Safari and let us know if you still see the issue @mikkelmk @MagnusHJensen?

Note to the team: This fix isn't straightforward and we need some thought to solve this. We would like to get this information before we land on a solution. Redirect refresh is not handled as expected in Safari, which causes this issue.

mikkelmk commented 2 years ago

@sameerag I'm still seeing the issue in Safari version 15.4 (17613.1.17.1.13), with msal-browser v2.23.0 and msal-react v1.3.2.

davecarlile commented 2 years ago

I solved this by adding a trailing slash to redirectUri. It turns out Safari was stripping the hash because it was not preceeded by a slash.

This works everywhere but safari and for certain users IOS Chrome: https://app.foo.com/auth/return#code=0.AVcAeMaRO0JAkE... This works everywhere I have tested: https://app.foo.com/auth/return/#code=0.AVcAeMaRO0JAkE...

jasonnutter commented 2 years ago

@davecarlile Interesting, thanks for the information.

leskodan commented 1 month ago

I solved this by adding a trailing slash to redirectUri. It turns out Safari was stripping the hash because it was not preceeded by a slash.

This works everywhere but safari and for certain users IOS Chrome: https://app.foo.com/auth/return#code=0.AVcAeMaRO0JAkE... This works everywhere I have tested: https://app.foo.com/auth/return/#code=0.AVcAeMaRO0JAkE...

I know this issue has been closed-- but adding the trailing slash solved the issue for me as well. Redirect was working fine on most browsers and devices except for some iOS Chrome users. Adding the trailing slash in my B2C App Registrant setting fixed it.