Skip to content

login_required does not clear stale account cache causing infinite redirect loop #8208

Description

@justinlau12

Core Library

MSAL.js (@azure/msal-browser)

Core Library Version

3.18.0

Wrapper Library

Not Applicable

Wrapper Library Version

None

Public or Confidential Client?

Public

Description

When handleRedirectPromise() returns a login_required error from Azure AD indicating session is dead, MSAL does not remove the stale account from cache and this is not explicitly stated in the docs anywhere.

The only guidance given is after acquireTokenSilent() fails, to fallback calling acquireTokenRedirect()/popup. However, in the case of when Azure AD rejects the redirect with login_required, MSAL simply throws the error back to the client with no clear guidance to clean the cache upon initialization here.

In RedirectClient.ts:

catch (e) {
            if (e instanceof AuthError) {
                (e as AuthError).setCorrelationId(this.correlationId);
                serverTelemetryManager.cacheFailedRequest(e);
            }
            throw e;
        }

it seems the cache should also be cleared above when MSAL detects the account is stale from AD, per OID standards when login is required this should always be the case.

Proposal:

.catch((e: AuthError) => {
    serverTelemetryManager.cacheFailedRequest(e);
    
    // login_required means Azure AD invalidated the session.
    // The cached account is stale and should be removed to prevent redirect loops.
    if (e instanceof InteractionRequiredAuthError && 
        e.errorCode === InteractionRequiredAuthErrorCodes.loginRequired) {
        
        // Get the account from the cached request
        const cachedRequest = this.browserStorage.getCachedNativeRequest() || 
                              this.browserStorage.getCachedRequest();
        
        if (cachedRequest?.account?.homeAccountId) {
            this.logger.warning(
                "Received login_required error. Removing stale account from cache."
            );
            this.browserStorage.removeAccount(
                cachedRequest.account.homeAccountId
            );
        }
    }
    
    throw e;
});

This causes an infinite redirect loop in our solution because:
MSAL gets initialized -> the error is simply caught and logged (client could account for this by clearing cache manually)
getAllAccounts() is called -> stale account is returned
getProfileImage() is triggered -> acquireTokenRedirect() ends up getting called, site is redirected back to itself with login_required since session is dead on AD.

This is something MSAL should account for or at least document because otherwise clients can easily encounter this redirect loop. Just like how in sign out the cache is cleared in MSAL, it feels like encountering this should also be the same.

Error Message

No response

MSAL Logs

No response

Network Trace (Preferrably Fiddler)

  • Sent
  • Pending

MSAL Configuration

auth: {
            clientId: "clientId",
            authority: "https://login.microsoftonline.com/consumers/",
            redirectUri: isMobile() ? window.location.origin : "authRedirectUri",
            navigateToLoginRequestUrl: true
        },
        cache: {
            cacheLocation: BrowserCacheLocation.LocalStorage,
            storeAuthStateInCookie: false,
        },
}

Relevant Code Snippets

It's very difficult to repro this situation. It's a rare scenario, but multiple users have encountered this by logging in on mobile where our `redirect` flow is utilized, waiting weeks for the session to expire, then visiting our site again where the infinite loop happens. The URL that gets constantly redirected to follows pattern as such:

https://apps.microsoft.com/#error=login_required&error_description=Silent+authentication+was+denied.+The+user+must+first+sign+in+and+if+needed+grant+the+client+application+access+to+the+scope+%27User.Read+openid+profile+offline_access%27.&state=...

Reproduction Steps

Repro steps are not consistent, described in Relevant Code Snippets. Video has been attached. Again, only happens on redirect flow (mobile on our site).

Fails to upload repro recording.

Expected Behavior

Cache is cleared when MSAL encounters login_required state from Azure AD otherwise it's a design gap (unless there's reasons for not clearing cache here). At least this scenario is documented so clients can have full context.

Identity Provider

Entra ID (formerly Azure AD) / MSA

Browsers Affected (Select all that apply)

Chrome, Firefox, Edge

Regression

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug-unconfirmedA reported bug that needs to be investigated and confirmedmsal-browserRelated to msal-browser packagepublic-clientIssues regarding PublicClientApplicationsquestionCustomer is asking for a clarification, use case or information.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions