microsoft-authentication-library-for-js: Unable to acquire token silently after B2C login and redirection to SPA redirectUri

Core Library

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

Core Library Version

2.24.0

Wrapper Library

Not Applicable

Wrapper Library Version

None

Description

My understanding is that the B2C flow with MSAL.js for a sign-in only policy works in the following way (please correct if mistaken):

  1. create msalInstance with the msal config
  2. call msalInstance.handleRedirectPromise()
  3. when the handleRedirectPromise promise returns, call loginRedirect with the correct scopes, which will redirect the user to a b2c login page, and on completion, navigate back to the SPA redirectUri
  4. once the the b2c login is complete, and the user is navigated back to the SPA redirectUri, we use the msalInstance.acquireTokenSilent with the correct scopes and current active account passed in as function option params, so we can get all the user information such as the accessToken. This is how we translate the information the MSAL stores in the browser (sessionStorage in this case) to user information and an access token that I can use.

Steps 1-3 are working as expected. The issue is specifically that on step 4, after the redirect happens when I login, I am unable to acquire the token. There are no active accounts (when I attempt to retrieve with MSAL.js), and I am also unable to acquire the token with acquireTokenSilent. The error I am getting is the following: BrowserAuthError: no_account_error: No account object provided to acquireTokenSilent and no active account has been set. Please call setActiveAccount or provide an account on the request.

The issue is that I can’t call setActiveAccount when the active account does not exist (nothing exists for msalInstance.getAllAccounts().

I am using the following code for step 4 - which is run after the the successful login redirect to the redirectUri:

        const accounts = msalInstance.getAllAccounts();
        if (accounts?.length > 0) {
         msalInstance.setActiveAccount(accounts[0]);
        }

        if (msalInstance.getActiveAccount()) {
         const msalScopes = { scopes: ["my-example-b2c-web-api-scope"] }
         msalInstance
            .acquireTokenSilent(msalScopes)
            .then((tokenResponse) => {
              // Do something with the tokenResponse
              console.log('acquireTokenSilent res', tokenResponse);
            })
            .catch((error) => {
              console.log('acquireTokenSilent error', error);
              if (error instanceof msal.InteractionRequiredAuthError) {
                // fallback to interaction when silent call fails
                return msalInstance.acquireTokenRedirect(
                  msalScopes
                );
              }
            });

The issue is that because this.msalInstance.getAllAccounts() is null (empty), I cannot successfully retrieve the token information from the acquireTokenSilent call.

However, if I sign-in the first time, and get navigated back to the redirectUri, and then I run the sign-in again (the msalInstance.handleRedirectPromise() and then loginRedirect() code), I will successfully get the accounts and the token response from acquireTokenSilent. So I know that it most likely is not a configuration issue, or something to do with scopes or permissions.

The issue that needs to be figured out, is how can the token be retrieved on the successful navigation from the b2c login to the SPA redirectUri? Having to trigger a sign-in twice is not doable for the app use-case. I am hoping there is a better way to do this and to fix the issue of being able to retrieve the token on that initial redirect navigation to the redirectUri of the SPA.

The expected behavior is that when the SPA redirectUri is hit, that I will be able to get the access token and other user information. acquireTokenSilent doesn’t seem to be working as expected for this use case.

Error Message

BrowserAuthError: no_account_error: No account object provided to acquireTokenSilent and no active account has been set. Please call setActiveAccount or provide an account on the request.

Msal Logs

n/a

MSAL Configuration

        const msalConfig = {
          auth: {
            clientId: 'example123',
            authority: 'b2c-example',
            redirectUri: 'localhost:4200/redirect-uri',
            knownAuthorities: ['my-b2c-web-api-scope'],
            postLogoutRedirectUri: 'localhost:4200/redirect-uri',
            navigateToLoginRequestUrl: true,
          },
          cache: {
            cacheLocation: 'sessionStorage',
            storeAuthStateInCookie: true,
          },
          system: {
            loggerOptions: {
              loggerCallback: (level, message, containsPii) => {
                if (containsPii) {
                  return;
                }
                switch (level) {
                  case msal.LogLevel.Error:
                    console.error(message);
                    return;
                  case msal.LogLevel.Info:
                    console.info(message);
                    return;
                  case msal.LogLevel.Verbose:
                    console.debug(message);
                    return;
                  case msal.LogLevel.Warning:
                    console.warn(message);
                    return;
                }
              },
            },
          },
        };

Relevant Code Snippets

     // initial code 
    msalInstance
      .handleRedirectPromise()
      .then((tokenResponse) => {
        if (tokenResponse === null) {
          // If the tokenResponse === null, you are not coming back from an auth redirect.
          msalInstance.loginRedirect(this.msalApiScopes);
        } else {
          // If the tokenResponse !== null, then you are coming back from a successful authentication redirect.
          console.log('success res', tokenResponse);
        }
      })
      .catch((err) => {
        // handle error, either in the library or coming back from the server
        console.log('err', err);
      });

        // code that runs after the redirectUri returns
        const accounts = msalInstance.getAllAccounts();
        if (accounts?.length > 0) {
          msalInstance.setActiveAccount(accounts[0]);
        }

         const msalScopes = { scopes: ["my-example-b2c-web-api-scope"] }
         msalInstance
            .acquireTokenSilent(msalScopes)
            .then((tokenResponse) => {
              // Do something with the tokenResponse
              console.log('acquireTokenSilent res', tokenResponse);
            })
            .catch((error) => {
              console.log('acquireTokenSilent error', error);
              if (error instanceof msal.InteractionRequiredAuthError) {
                // fallback to interaction when silent call fails
                return msalInstance.acquireTokenRedirect(
                  msalScopes
                );
            });


Reproduction Steps

see initial description

Expected Behavior

Be able to get token through acquireTokenSilent method directly after the redirect back to the SPA redirectUri after the b2c login is completed. Not having to go through the handleRedirectPromise a second time

Identity Provider

Azure B2C Basic Policy

Browsers Affected (Select all that apply)

Chrome

Regression

No response

Source

External (Customer)

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 15 (8 by maintainers)

Most upvoted comments

@danielvoigt I am currently looking into this , thanks for your patience!