question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

AcquireTokenPopup for someone other than logged on user

See original GitHub issue

Library

  • msal@1.x.x or @azure/msal@1.x.x

Description

This question relates to msal@1.2.x even though we have SPA which uses @azure/msal-angular@0.1.4-beta.2 and which we’re trying to upgrade to @azure/msal-angular@1.0.0-beta.2 (therefore bringing in the msal@1.2.x peer dependency).

Previously when we were using @azure/msal-angular@0.1.4-beta.2, we took a dependency of @azure/msal-angular/msal@0.2.4. Our use case worked with @azure/msal-angular/msal@0.2.4 and it no longer works with msal@1.2.x.

Our use case is as follows. There is a main logged on user in our app who performs various activities in the app and at some point needs to perform a privileged operation that requires elevated permissions. The user does not have those permissions and needs to call over a manager to type in his or her credentials. Barring the inherent risk of the manager typing his or her credentials on a user workstation (which we plan on curing by enabling 2FA for the manager), this scenario worked fine when we used acquireTokenPopup in @azure/msal-angular/msal@0.2.4 using the following code:

const user = this.authService.getUser();
return from(this.authService.acquireTokenPopup(
  ['api://[our_api_uri]/user_impersonation`],
  'https://login.microsoftonline.com/[tenantId],
   user,
   'prompt=login&forceRefresh=true'
));

The manager would sign in using his or her own credentials and this would give us an access token which we then used to call our API to execute the privileged operation as the manager. This did not affect the currently logged on user and after the privileged operation was executed, the user would continue performing functions in the app as himself/herself. The manager therefore provided an access token which was used one time, just for the privileged operation.

After upgrading to msal@1.2.2-beta.0 which is a peer dependency of @azure/msal-angular@1.0.0-beta.2, we needed to change the above code to the following:

const account = this.authService.getAccount();
return from(this.authService.acquireTokenPopup({
  scopes: [environment.api.audience + '/user_impersonation'],
  account: account,
  prompt: 'login',
  forceRefresh: true
})).pipe(
  map(authResponse => authResponse.accessToken)
);

Seems like a simple enough upgrade, right? Unfortunately not. The acquireTokenPopup returns a null accessToken in the updated code. After digging around and debugging the code inside UserAgentApplication, I found that even though Azure AD was returning an accessToken for the correct account (the manager) in the URL hash, it was being discarded in the following code within the saveTokenFromHash method in UserAgentApplication:

// Process access_token
if (hashParams.hasOwnProperty(ServerHashParamKeys.ACCESS_TOKEN)) {
    this.logger.info("Fragment has access token");

    // retrieve the id_token from response if present
    if (hashParams.hasOwnProperty(ServerHashParamKeys.ID_TOKEN)) {
        idTokenObj = new IdToken(hashParams[ServerHashParamKeys.ID_TOKEN]);
        response.idToken = idTokenObj;
        response.idTokenClaims = idTokenObj.claims;
    } else {
        idTokenObj = new IdToken(this.cacheStorage.getItem(PersistentCacheKeys.IDTOKEN));
        response = ResponseUtils.setResponseIdToken(response, idTokenObj);
    }

So in the above, hashParams[ServerHashParamKeys.ACCESS_TOKEN] is not used at all in the resultant response. What we get back is actually a response with the idToken set for the currently logged on user instead of a response with an accessToken for the manager.

The questions are therefore:

  1. Is this a bug, and if not,
  2. Is there a way that we can get an access token for the manager using msal@1.2.x without affecting the currently logged on user and also without affecting any associated session or local storage for the user?

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:8 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
Ivan-Lcommented, Mar 3, 2020

@jasonnutter Apologies for the late response, I have been under pressure to meet certain deadlines.

I did some more digging and narrowed down the issue even further by debugging both the old version and new version of UserAgentApplication.

In the old version 0.2.4 of the processCallback method of UserAgentApplication, the access token in our case is obtained regardless of what the saveTokenFromHash does just before that. Then, the token received callback is called with the correct token. This is why our scenario worked in the past.

In the newer versions, the AuthResponse class was introduced and reliance is placed on saveTokenFromHash populating the AuthResponse correctly. The only “post processing” that processCallback does after saveTokenFromHash is called, is that the token type is set in the response. The processCallback method no longer tries to get the access token from the hash anymore.

So my suggestion would be to update the saveTokenFromHash method, specifically the block of code that I pasted previously, to not only try to get the ID token from the hash but also get the access token from the hash as well. The following code:

// Process access_token
if (hashParams.hasOwnProperty(ServerHashParamKeys.ACCESS_TOKEN)) {
    this.logger.info("Fragment has access token");

    // retrieve the id_token from response if present
    if (hashParams.hasOwnProperty(ServerHashParamKeys.ID_TOKEN)) {
        idTokenObj = new IdToken(hashParams[ServerHashParamKeys.ID_TOKEN]);
        response.idToken = idTokenObj;
        response.idTokenClaims = idTokenObj.claims;
    } else {
        idTokenObj = new IdToken(this.cacheStorage.getItem(PersistentCacheKeys.IDTOKEN));
        response = ResponseUtils.setResponseIdToken(response, idTokenObj);
    }
    ...

Could change to:

// Process access_token
if (hashParams.hasOwnProperty(ServerHashParamKeys.ACCESS_TOKEN)) {
    this.logger.info("Fragment has access token");

    /***** START NEW CODE ******/
    // retrieve the access_token from hashParams since we already know it's there
    response.accessToken = hashParams[ServerHashParamKeys.ACCESS_TOKEN];
    /***** END NEW CODE ******/

    // retrieve the id_token from response if present
    if (hashParams.hasOwnProperty(ServerHashParamKeys.ID_TOKEN)) {
        idTokenObj = new IdToken(hashParams[ServerHashParamKeys.ID_TOKEN]);
        response.idToken = idTokenObj;
        response.idTokenClaims = idTokenObj.claims;
    } else {
        idTokenObj = new IdToken(this.cacheStorage.getItem(PersistentCacheKeys.IDTOKEN));
        response = ResponseUtils.setResponseIdToken(response, idTokenObj);
    }
    ...

After the above change, the response should contain the access token as well and it would no longer be discarded.

As a separate issue, I do not think that the handling of the ID token is quite correct in the existing code because the code falls back to using the existing ID token in the cache if the hash does not contain one. In our case someone other than the currently logged on user enters their credentials when the acquireTokenPopup method is called, yet we get back an AuthResponse response with the ID token set as the currently logged on user. Perhaps the more correct behavior is to keep the ID token as null in the AuthResponse if an ID token isn’t present in the hash? As mentioned though, this is a separate issue that should be debated.

0reactions
Ivan-Lcommented, Mar 12, 2020

The issue has been fixed with msal@1.2.2-beta.2, thanks @jasonnutter!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Prompt behavior with MSAL.js - Microsoft Entra
In this case, the user will see a brief popup window but will not be prompted for a credential entry. Next steps. Single...
Read more >
Microsoft Authentication Library for JavaScript (MSAL.js) - npm
When the login methods are called and the authentication of the user is completed by the Azure AD service, an id token is...
Read more >
msal.js - Is it ever advisable to use loginPopup or ...
One common reason to choose popups over redirects is that redirects can only be used in the top frame of your application.
Read more >
Use msal-browser in SPFx solutions to access Graph API
I will use the acquireTokenPopup for interactive login but you could also easily implement the redirect method if you need to. protected ...
Read more >
Adding MSAL And React - Azure Serverless Quickstart - GitBook
use -msal.tsx #expose provider methods ... acquireTokenPopup(loginRequestConfig); ... //allows for anyone to register not just AAD accounts.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found