0
votes

I am upgrading an app using msal.js v1.3 to v2.3 and I'm having a problem retreiving the access token once I get my id token.

I initialize the handleRedirectPromise in my constructor. Then, when the user clicks the login button, I call loginRedirect and pass in an object that has the openid scope and the scope from my separately registered api. This works well, the id token comes back and I call acquireTokenSilent to retreive my access token. I pass an object that has my registered api's scope and account from the loginRedirect call into this function.

The problem is that the authorization response from the acquireTokenSilent has an empty access token. The result from the token endpoint looks like:

client_info: "xx"
id_token: "xx"
not_before: 1602895189
refresh_token: "xx"
refresh_token_expires_in: 1209600
scope: ""
token_type: "Bearer"

It doesn't have an access token, but it does specifiy the token type as Bearer There is no access token in the response and it looks like the scopes property returning is empty. Here is my code:

    private msalConfig: Msal.Configuration = {
        auth: {
            clientId: environment.clientID,
            authority: 'https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/B2C_1_DefaultSignInSignUp',
            knownAuthorities: ['<tenant>.b2clogin.com'],
            navigateToLoginRequestUrl: true, 
        },
        cache: {
            cacheLocation: 'sessionStorage',
            storeAuthStateInCookie: false, 
        }
    };
    private loginRequest: Msal.RedirectRequest = {
        scopes: ['openid' ,  'offline_access', 'https://<tenant>.onmicrosoft.com/api/read' ] 
    };

    private accessTokenRequest: Msal.SilentRequest = {
        scopes:  ['https://<tenant>.onmicrosoft.com/api/read'] ,
        account: null
        
    };
 constructor() {
        const _this = this;
        this.msalInstance = new Msal.PublicClientApplication(this.msalConfig);
        this.aquireSilent = (request: Msal.SilentRequest): Promise<Msal.AuthenticationResult> => {
            return _this.msalInstance.acquireTokenSilent(request).then(
                access_token => {
                    _this.cacheExpiration(access_token.expiresOn);
                    _this.isLoggedIn$.next(true);
                    return access_token;
                },
                function (reason) {
                    console.error(reason);
                },
            );
        };

        this.msalInstance
            .handleRedirectPromise()
            .then((tokenResponse: Msal.AuthenticationResult) => {
                if (tokenResponse !== null) {
                    const id_token = tokenResponse.idToken;
                    const currentAccounts = this.msalInstance.getAllAccounts()
                    this.accessTokenRequest.account = currentAccounts[0];
                    this.aquireSilent(this.accessTokenRequest)
                } 
            })
            .catch(error => {
                console.error(error);
            });
    }

    public login() {
        this.msalInstance.loginRedirect(this.loginRequest);
    }

Why is the access token not coming back from the token endpoint? Does it have to do with the scopes returning empty? I tried removing the scopes and putting in invalid entries and an error gets raised so I know my request going out is at least valid. Also, just to verify, I have 2 app registrations in AAD, one I created for my spa that has code flow and my older registration I have for my api with an exposed api and scope.

1
Without a scope in the auth request, there is no access token acquired.Jas Suri - MSFT
@JasSuri-MSFT which part is the auth Request? Both my loginRequest and my accessToken request parameters have scopes. Do I need to add it to my b2c configuration as well?afriedman111
Your code looks good actually. Check the Application storage (session+local) in the Browsers dev tools to see if MSAL has thrown an errorJas Suri - MSFT
No errors. Also the request is a valid one. Above I listed the result from the token endpoint which is called when acquireTokenSilent is invoked. That doesn't have an access token on it, so it makes be believe there is something wrong with my original request or my app registration.afriedman111
The populated value in jwt.ms is the id token. You need to put the url in notepad to see if access token also returned. You must click grant admin consent. Follow the document exactly.Jas Suri - MSFT

1 Answers

0
votes

acquireTokenSilent will return an access token only if there is already an entry for that token in the cache. So if for some reason the token was never obtained previously (via loginRedirect, for instance), it will not be able to acquire it silently.

That seems to be the issue in your case. You are mixing scopes for different resources in your loginRequest, and that's perhaps causing the issue in the new version of the library (access tokens are issued per-resource-per-scope(s). See this doc for more) Try modifying your loginRequest object like this:

  private loginRequest: Msal.RedirectRequest = {
       scopes: ['openid',  'offline_access' ],
       extraScopesToConsent:['https://<tenant>.onmicrosoft.com/api/read']

   };

Also, the recommended pattern of usage with acquireTokenSilent is that you should fall back to an interactive method (e.g. acquireTokenRedirect) if the acquireTokenSilent fails for some reason.

So I would modify it as:

    this.aquireSilent = (request: Msal.SilentRequest): Promise<Msal.AuthenticationResult> => {
        return _this.msalInstance.acquireTokenSilent(request).then(
            access_token => {
                 // fallback to interaction when response is null
                 if (access_token === null) {
                   return _this.msalInstance.acquireTokenRedirect(request);
                 }
                _this.cacheExpiration(access_token.expiresOn);
                _this.isLoggedIn$.next(true);
                return access_token;
            },
            function (reason) {          
               if (reason instanceof msal.InteractionRequiredAuthError) {
                  // fallback to interaction when silent call fails
                  return _this.msalInstance.acquireTokenRedirect(request);
               } else {
                  console.warn(reason);   
               }
            },
        );
    };

A similar issue is discussed here