microsoft-authentication-library-for-js icon indicating copy to clipboard operation
microsoft-authentication-library-for-js copied to clipboard

Updating to msal-browser 4.9.0 login doesn't work anymore

Open StantonCoffey89 opened this issue 11 months ago • 13 comments

Core Library

MSAL.js (@azure/msal-browser)

Core Library Version

4.9.0

Wrapper Library

MSAL Angular (@azure/msal-angular)

Wrapper Library Version

4.0.7

Public or Confidential Client?

Public

Description

(please go easy on me, this is my first time submitting a bug report)

Before updating my NPM packages I was able to have my angular application log a user in. After updating, the login processing doesn't work anymore. If I revert my msal-angular and msal-browser packages back to 4.0.7 and 4.8.0, respectively, then the application logs in fine.

Error Message

No response

MSAL Logs

After updating my npm packages, the login process after a redirect seems to get stuck after the "initializeEnd". It doesn't seem to handle the redirect portion and finish logging the user in.

MsalBroadcastService inProgress$ startup MsalBroadcastService msalSubject$ msal:initializeStart {eventType: 'msal:initializeStart', interactionType: null, payload: null, error: null, timestamp: 1743002095450} [Wed, 26 Mar 2025 15:14:55 GMT] : [] : @azure/[email protected] : Info - CacheManager:getIdToken - Returning ID token [Wed, 26 Mar 2025 15:14:55 GMT] : [] : @azure/[email protected] : Info - CacheManager:getIdToken - Returning ID token Angular is running in development mode. [Wed, 26 Mar 2025 15:14:55 GMT] : [] : @azure/[email protected] : Info - CacheManager:getIdToken - Returning ID token MsalBroadcastService msalSubject$ msal:initializeEnd {eventType: 'msal:initializeEnd', interactionType: null, payload: null, error: null, timestamp: 1743002095482} [Wed, 26 Mar 2025 15:14:55 GMT] : [] : @azure/[email protected] : Info - handleRedirectPromise called but there is no interaction in progress, returning null.

Network Trace (Preferrably Fiddler)

  • [ ] Sent
  • [ ] Pending

MSAL Configuration

export function loggerCallback(logLevel: LogLevel, message: string) {
    console.log(message);
}

export function MSALInstanceFactory(): IPublicClientApplication {
    return new PublicClientApplication({
        auth: {
            clientId: '<client-id>',
            authority: "https://login.microsoftonline.com/<tenent-id>",
            redirectUri: document.location.origin,
            postLogoutRedirectUri: '/',
        },
        cache: {
            cacheLocation: BrowserCacheLocation.SessionStorage,
        },
        system: {
            allowPlatformBroker: false,
            loggerOptions: {
                loggerCallback,
                logLevel: LogLevel.Info,
                piiLoggingEnabled: true,
            },
        },
    });
}

export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
    const protectedResourceMap = new Map<string, Array<string>>();
    protectedResourceMap.set('<url>', ['<scope>']);
    return {
        interactionType: InteractionType.Redirect,
        protectedResourceMap,
    };
}

export const appConfig: ApplicationConfig = {
    providers: [
        provideZoneChangeDetection({ eventCoalescing: true }),
        provideRouter(routes, withHashLocation()),
        provideHttpClient(withInterceptorsFromDi(), withFetch()),
        {
            provide: HTTP_INTERCEPTORS,
            useClass: MsalInterceptor,
            multi: true,
        },
        {
            provide: MSAL_INSTANCE,
            useFactory: MSALInstanceFactory,
        },
        {
            provide: MSAL_INTERCEPTOR_CONFIG,
            useFactory: MSALInterceptorConfigFactory,
        },
        MsalService,
        MsalGuard,
        MsalBroadcastService
    ]
};

Relevant Code Snippets

async loginAzureAd() {
        return new Promise<boolean>(async (resolve) => {
            this.msalBroadcastService.msalSubject$.subscribe(event => {
                console.log('MsalBroadcastService', 'msalSubject$', event.eventType, (event.error?.cause ?? '') + (event.error?.message ?? ''), event)
            })
            this.msalBroadcastService.inProgress$.subscribe(status => {
                console.log('MsalBroadcastService', 'inProgress$', status)
                if (status == InteractionStatus.None) {
                    let activeAccount = this.msalService.instance.getActiveAccount();
                    if (activeAccount) {
                        console.log('already an active account', activeAccount)
                        resolve(true)
                    } else {
                        let allAccounts = this.msalService.instance.getAllAccounts()
                        if (allAccounts.length == 0) {
                            console.log('no msal accounts')
                            this.msalService.instance.loginRedirect()
                        } else {
                            console.log('activating first account', allAccounts)
                            this.msalService.instance.setActiveAccount(allAccounts[0]);
                            resolve(true)
                        }
                    }
                }
            });
            this.msalService.handleRedirectObservable().subscribe();
        })
    }

Reproduction Steps

  1. update npm packages so msal-angular is 4.0.8 and msal-browser is 4.9.0
  2. running the application the login process gets stuck after a redirect and never gets to an inProgress$ status of "none"
  3. revert npm packages to msal-angular is 4.0.7 and msal-browser is 4.8.0
  4. running the application the login process doesn't get stick after redirect. The progress status gets "none"

Expected Behavior

The following log outputs are what login normally looks like after a redirect back to my page. You can see all the steps happen and the MsalBroadcastService.inProgress$ gets to a state of "none", which I'm expecting.

MsalBroadcastService inProgress$ startup MsalBroadcastService msalSubject$ msal:initializeStart {eventType: 'msal:initializeStart', interactionType: null, payload: null, error: null, timestamp: 1742999991666} [Wed, 26 Mar 2025 14:39:51 GMT] : [] : @azure/[email protected] : Info - CacheManager:getIdToken - Returning ID token [Wed, 26 Mar 2025 14:39:51 GMT] : [] : @azure/[email protected] : Info - CacheManager:getIdToken - Returning ID token Angular is running in development mode. [Wed, 26 Mar 2025 14:39:51 GMT] : [] : @azure/[email protected] : Info - CacheManager:getIdToken - Returning ID token MsalBroadcastService msalSubject$ msal:initializeEnd {eventType: 'msal:initializeEnd', interactionType: null, payload: null, error: null, timestamp: 1742999991692} [Wed, 26 Mar 2025 14:39:51 GMT] : [] : @azure/[email protected] : Info - CacheManager:getIdToken - Returning ID token MsalBroadcastService msalSubject$ msal:handleRedirectStart {eventType: 'msal:handleRedirectStart', interactionType: 'redirect', payload: null, error: null, timestamp: 1742999991694} MsalBroadcastService inProgress$ handleRedirect [Wed, 26 Mar 2025 14:39:51 GMT] : [0195d2e5-358e-7f58-9785-bc2f05dd8d23] : [email protected] : Info - handleRedirectPromise called but there is no interaction in progress, returning null. MsalBroadcastService msalSubject$ msal:handleRedirectEnd {eventType: 'msal:handleRedirectEnd', interactionType: 'redirect', payload: null, error: null, timestamp: 1742999991695} MsalBroadcastService inProgress$ none [Wed, 26 Mar 2025 14:39:51 GMT] : [] : @azure/[email protected] : Info - CacheManager:getIdToken - Returning ID token already an active account

Identity Provider

Entra ID (formerly Azure AD) / MSA

Browsers Affected (Select all that apply)

Chrome

Regression

@azure/msal-angular 4.0.7

StantonCoffey89 avatar Mar 26 '25 15:03 StantonCoffey89

I had to roll back to:

"@azure/msal-angular": "4.0.7",
"@azure/msal-browser": "4.8.0",

4.9.0 redirect caching changes caused wiped out accounts on browser refresh. Doesn't happen with 4.8.0.

gcrockenberg avatar Mar 26 '25 17:03 gcrockenberg

I am also unable to login after upgrading to 4.9.0. Same issue of handleRedirectPromise response being null. I am using just @azure/msal-browser with no wrapper lib. Downgrading to 4.8.0 fixes it.

klandell avatar Mar 26 '25 17:03 klandell

@azure/msal-browser v4.9.1 was released yesterday. I tried that version, but it seems to have the same issue.

StantonCoffey89 avatar Mar 27 '25 12:03 StantonCoffey89

I'm having the same problem, tried out v4.9.1 and the problem remained. Rolled back to @azure/msal-browser v4.8.0 and @azure/msal-angular v4.0.7 and logins worked as expected with redirect flow. With the new versions the InteractionStatus appears to get stuck in "startup" state.

neilzuk avatar Mar 27 '25 14:03 neilzuk

I'm having a slightly different issue, but potentially related. After logging out and redirecting, I get stuck in the handleRedirect state. After trying it with the samples and older versions, it appears that the problematic area was introduced in the handleRedirectPromiseInternal function as part of Redirect flow temporary cache refactor (#7648).

yitigerliu avatar Mar 28 '25 14:03 yitigerliu

I'm having the same problem, tried out v4.9.1 and the problem remained. Rolled back to @azure/msal-browser v4.8.0 and @azure/msal-angular v4.0.7 and logins worked as expected with redirect flow. With the new versions the InteractionStatus appears to get stuck in "startup" state.

We had to rollback to @azure/msal-browser v4.8.0. We are also using the redirect flow.

plamber avatar Mar 31 '25 08:03 plamber

I'm also having the issue for my react app where redirecting results in a loop, neither v4.9.0 and v4.8.0 work, I have to revert back to v4.7.0

kimpham301 avatar Mar 31 '25 15:03 kimpham301

I'm also having the same issue with msal-react and msal-browser 4.9.1, I had to revert back to 4.8.0

pascual-cano-allhuman avatar Mar 31 '25 21:03 pascual-cano-allhuman

aaaaaand... they silently killed 4.9.x - no word in this issue :D LOL!

wgebczyk avatar Apr 01 '25 06:04 wgebczyk

We are working on reproducing on our end, we are not seeing it in our samples. If someone can share steps to repro this for msal-browser (msal-angular is shared in the issue desc), including msal configuration, is the migration done with reloading the page and the APIs used, it will be helpful to sort this early.

The deprecation announcement is made in response to the issues reported here to ensure other folks are not impacted.

sameerag avatar Apr 01 '25 17:04 sameerag

Hey @StantonCoffey89 I am able to reproduce an issue with the interaction status not clearing out properly after user logs out (logoutRedirect) and then logs back in using the acquireTokenRedirect for msal-browser. Is this the same scenario you are facing or anyone else here is? If you can provide a full MSAL trace logs from the console, that would be helpful in confirming this use case.

lalimasharda avatar Apr 02 '25 01:04 lalimasharda

I have a pretty simple use case. I hope this can help with debugging.

On location change, if the there is an account, the content renders, otherwise the login function of my AuthProvider is called. This worked very well in 4.8, and as mentioned, const res = await c.handleRedirectPromise(); is always null in 4.9.

e.g.

useEffect(() => {
  if (!account && !isLoggingIn) {
    startLogin(async () => {
      await login(location);
    });
  }
}, [account, isLoggingIn, login, location]);

file: "~/lib/auth/msal-client";

import { BrowserCacheLocation, createStandardPublicClientApplication } from "@azure/msal-browser";

import { getConfig } from "~/lib/app-config";

const baseURL = window.location.origin;

export default (() => {
  const config = getConfig();
  return createStandardPublicClientApplication({
    auth: {
      clientId: config.AZURE_CLIENT_ID,
      authority: `https://login.microsoftonline.com/${config.AZURE_TENANT_ID}`,
      redirectUri: `${baseURL}/auth/redirect`,
      postLogoutRedirectUri: `${baseURL}/auth/logout-success`,
      navigateToLoginRequestUrl: false,
    },
    cache: { cacheLocation: BrowserCacheLocation.SessionStorage },
  });
})();

export const getApiScopes = () => {
  const config = getConfig();
  return [`api://${config.AZURE_CLIENT_ID}/default`];
};

export const getMsScopes = () => ["User.Read"];

export const getScopes = () => [...getApiScopes(), ...getMsScopes()];

file: "~/components/ux/authentication/Authentication/AuthenticationProvider"

import { ReactNode, use, useCallback, useEffect, useState } from "react";

import { EventType } from "@azure/msal-browser";
import { Location, NavigateFunction } from "react-router";

import { AuthenticationContext } from "~/components/ux/authentication/AuthenticationContext";
import msalClient, { getScopes } from "~/lib/auth/msal-client";
import HttpError, { HttpStatus } from "~/lib/error/http-error";

type State = { to: string };

export default function AuthenticationProvider(props: { children?: ReactNode }) {
  const c = use(msalClient);

  const [account, setAccount] = useState(() => {
    let active = c.getActiveAccount();
    if (!active) {
      const all = c.getAllAccounts();
      if (all.length === 1) {
        active = all[0];
        c.setActiveAccount(active);
      }
    }
    return active;
  });

  useEffect(() => {
    const cid = c.addEventCallback((ev) => {
      if (ev.eventType === EventType.LOGOUT_SUCCESS) {
        c.setActiveAccount(null);
      }
    });
    return () => {
      if (cid) c.removeEventCallback(cid);
    };
  }, [c]);

  const login = useCallback(
    async (location: Location) => {
      if (!account) {
        await c.clearCache();
        await c.loginRedirect({
          state: encodeState({ to: location.pathname + location.search + location.hash }),
          scopes: getScopes(),
        });
      }
    },
    [account, c],
  );

  const logout = useCallback(async () => {
    if (account) {
      await c.logoutRedirect({ account });
    }
  }, [account, c]);

  const handleRedirect = useCallback(
    async (navigate: NavigateFunction) => {
      try {
        const res = await c.handleRedirectPromise();
        if (res) {
          const { account, state } = res;

          c.setActiveAccount(account);
          setAccount(account);
          if (state) {
            const to = decodeState(state)?.to;
            navigate(to ?? "/");
          }
        } else {
          throw new HttpError(HttpStatus.Unauthorized);
        }
      } catch (err) {
        console.error(err);
        throw new HttpError(HttpStatus.Unauthorized, undefined, { cause: err });
      }
    },
    [c],
  );

  const value = {
    account,
    login,
    logout,
    handleRedirect,
  };

  return <AuthenticationContext.Provider value={value} {...props} />;
}

function encodeState(state: State) {
  return window.btoa(JSON.stringify(state));
}

function decodeState(str: string) {
  return JSON.parse(window.atob(str)) as State;
}

file: "~/components/ux/authentication/AuthenticationContext"

import { createContext } from "react";

import { AccountInfo } from "@azure/msal-browser";
import { Location, NavigateFunction } from "react-router";

type AuthenticationState = {
  account: AccountInfo | null;
  login: (location: Location) => Promise<void>;
  logout: () => Promise<void>;
  handleRedirect: (navigate: NavigateFunction) => Promise<void>;
};

export const AuthenticationContext = createContext<AuthenticationState | null>(null);

file: "~/components/ux/authentication/CompleteAuthentication"

This is the page for the route that matches the authentication redirect URI. It calls the handler to complete authentication in the auth provider.

import { useEffect, useTransition } from "react";

import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";

import AbsoluteFill from "~/components/ux/AbsoluteFill";
import FillLoader from "~/components/ux/FillLoader";
import useAuthentication from "~/hooks/use-authentication";

export default function CompleteAuthentication() {
  const { account, handleRedirect } = useAuthentication();
  const [isHandlingRedirect, startHandleRedirect] = useTransition();

  const navigate = useNavigate();
  const { t: uxt } = useTranslation("ux");

  useEffect(() => {
    if (!account && !isHandlingRedirect) {
      startHandleRedirect(async () => {
        await handleRedirect(navigate);
      });
    }
  }, [account, isHandlingRedirect, handleRedirect, navigate]);

  return (
    <AbsoluteFill>
      <FillLoader label={uxt("authentication.loadingLabel")} />
    </AbsoluteFill>
  );
}

klandell avatar Apr 02 '25 14:04 klandell

Here are 2 trace outputs. One where I'm able to login successfully, the other where login fails.

Both scenarios start with a browser with no users logged in.

The failure scenario (v4.9.1) gets stuck after the MsalService initialization steps end. If you look at my "loginAzureAd()" function above you'll see that I'm waiting for the "inProgress$" status to change to "None" for me to call the "loginRedirect()" function. But the status never changes.

The success scenario (v4.8.0) is successful because the MsalService initialization steps end AND the "inProgress$" status changes to a value of "None" meaning I can now call the "loginRedirect()" function.

successful-login-sequence-v4.0.7-v4.8.0-1743603538226.log failure-login-sequence-v4.0.8-v4.9.1-1743604266578.log

StantonCoffey89 avatar Apr 02 '25 14:04 StantonCoffey89

Hi all, We're running into the same issue described here: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/7663#issuecomment-2771039266.

Is there any update or workaround available at this point? We'd really appreciate any insights or guidance.

Thanks in advance!

maaaNu avatar Apr 08 '25 07:04 maaaNu

Can folks try out the latest builds from today and let us know if this issue is fixed? Thank you!

lalimasharda avatar Apr 08 '25 23:04 lalimasharda

Hello, For me all is working good! Thanks

Tweentyy avatar Apr 09 '25 08:04 Tweentyy

I've updated this morning and can confirm my Angular app is working as expected again, thank you!

neilzuk avatar Apr 09 '25 09:04 neilzuk

4.10.0 works for me. Thanks.

klandell avatar Apr 09 '25 15:04 klandell

I've updated to 4.10.0 and all is working now. Thanks!

StantonCoffey89 avatar Apr 10 '25 13:04 StantonCoffey89