Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSAL fails to create separate hidden iframes for each token request based on target authority, resulting in returning the wrong token or error to registered callbacks #1225

Closed
1 of 4 tasks
keystroke opened this issue Jan 20, 2020 · 7 comments · Fixed by #1267
Assignees
Labels
bug A problem that needs to be fixed for the feature to function as intended.

Comments

@keystroke
Copy link

keystroke commented Jan 20, 2020

Library

  • msal@1.2.0 or @azure/msal@1.x.x
  • @azure/msal-angular@0.x.x
  • @azure/msal-angular@1.x.x
  • @azure/msal-angularjs@1.x.x

Framework

None.

Description

[UPDATE]

See my latest comment; I've renamed the issue according to what I believe is the root cause. It seems that MSAL is creating hidden iframes identified only by target scopes in the token request, but failing to include target authority. This results in multiple concurrent token requests with distinct authorities but identical scopes to be joined together and all of them receive the same result from that single hidden iframe!

I have also found a hacky workaround, the mechanism for which might be usable in more scoped scenarios if people hit this issue, though creating a generic version of the workaround would be difficult (easier to just fix in MSAL directly to scope target hidden iframes by authority and scopes).

[/UPDATE]

When requesting tokens for multiple different authorities at the same time, the same response (error or result) will be return to all the .then or .catch handlers.

Details described in this post are primarily for above issue, but I notice many issues trying to sign-in a user and make API calls to multiple different directories, all concurrently. For example, only one popup may be active at a time. This is perhaps fine, but while a popup is active, any silent token requests will terminate the popup... I would expect that silent token requests should not affect a running popup request, and they should be isolated somehow.

In general, please advise on how to setup using MSAL if I want to sign a user into their home directory, and then proceed to acquire many different tokens concurrently, including handling popups for each necessary consent (e.g. in below issue example you will see 9 directories I am attempting to authenticate with, and some of them require consent; I have built a wrapper to allow the user to proceed with granting consent via popups one at a time, and once consent is granted, I acquire other tokens in each directory and make other API calls, and all of this means I have build an insanely complicated wrapper around MSAL that ensures token requests are processed serially as necessary which is obviously terrible). For example do I need to be creating separate MSAL client objects for each target directory? What about the caching behavior? etc.

Security

Is this issue security related?

I would say yes, because this error isn't obvious at first and when testing my web app I was taking actions against the wrong tenant! Especially since with the new graph APIs, the target tenant is only in the token, not in the URI like it used to be!

Regression

Don't recall. I've been waiting for MSAL to mature fully to do my basic operations:

  1. sign-in user
  2. lookup all directories for that user
  3. get the tenant details of each of those tenants

In one way or another, I haven't been able to achieve this with MSAL due to issues like this. I understand there were recent changes to cache behavior. PLEASE ADVISE how I need to set this up to accomplish this. Do I need separate instances of MSAL clients?

Configuration

Please provide your MSAL configuration options.

const msal = new Msal.UserAgentApplication({
    auth: {
        clientId: 'a963b646-fa01-4c06-b302-42c6ed1b2774',
        authority: 'https://login.microsoftonline.com/common'
    }
});

Reproduction steps

  1. sign-in user using common (I used redirect flow)
  2. on page load, if msal.getAccount(), proceed to get a silent token for login scopes
  3. if (2) is successful, attempt to acquire token silent for multiple other directories
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>MSAL Bug</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.4/bluebird.min.js" class="pre"></script>
    <script src="https://alcdn.msauth.net/lib/1.2.0/js/msal.min.js"></script>
    <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.4.1.min.js"></script>
</head>

<body>

    <script>
        'use strict';
        $(document).ready(function (e) {

            // MSAL login redirect flow...

            const msal = new Msal.UserAgentApplication({
                auth: {
                    clientId: 'a963b646-fa01-4c06-b302-42c6ed1b2774',
                    authority: 'https://login.microsoftonline.com/common'
                }
            });

            msal.handleRedirectCallback(function (error, response) {
                console.log('MSAL redirect callback: ', { error: error, response: response });
            });

            if (msal.isCallback(window.location.hash)) {
                return;
            }

            if (!msal.getAccount()) {
                msal.loginRedirect({
                    scopes: ['openid', 'User.Read'],
                    prompt: 'select_account'
                });
                return;
            }

            // App code

            msal.acquireTokenSilent({ scopes: ['openid'], authority: msal.config.auth.authority })
                .then(beginTenantDiscoverySerial)
                .catch(console.error.bind(console));

            function beginTenantDiscoveryConcurrent(loginToken) {
                console.log('Beginning tenant discovery concurrent:', loginToken);
                for (let i = 1; i <= 9; i++) {
                    let tenant = 'azurestackci0' + i + '.onmicrosoft.com';
                    msal.acquireTokenSilent({
                        scopes: ['openid'],
                        authority: 'https://login.microsoftonline.com/' + tenant
                    }).then(function (token) {
                        console.log('Tenant ' + tenant + ':', getAccessTokenClaims(token).tid);
                    }).catch(function (error) {
                        console.log('Tenant ' + tenant + ':', error.message);
                    });
                }
            }

            function beginTenantDiscoverySerial(loginToken) {
                console.log('Beginning tenant discovery serial:', loginToken);
                let tenants = [];
                for (let i = 1; i <= 9; i++) {
                    let tenant = 'azurestackci0' + i + '.onmicrosoft.com';
                    tenants.push(tenant);
                }
                let next = function () {
                    let tenant = tenants.shift();
                    if (!tenant) return;
                    msal.acquireTokenSilent({
                        scopes: ['openid'],
                        authority: 'https://login.microsoftonline.com/' + tenant
                    }).then(function (token) {
                        console.log('Tenant ' + tenant + ':', getAccessTokenClaims(token).tid);
                        try { next(); } catch (error) { console.error(error); }
                    }).catch(function (error) {
                        console.log('Tenant ' + tenant + ':', error.message);
                        try { next(); } catch (error2) { console.error(error2); }
                    });
                };
                next();
            }
        });

        String.prototype.replaceAll = function (str1, str2, ignore) {
            return this.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g, "\\$&"), (ignore ? "gi" : "g")), (typeof (str2) == "string") ? str2.replace(/\$/g, "$$$$") : str2);
        }

        function getAccessTokenClaims(token) {
            let b64 = token.accessToken.split('.')[1].replaceAll('-', '+').replaceAll('_', '/');
            if ((b64 % 4) == 2) b64 += '==';
            if ((b64 % 4) == 3) b64 += '=';
            return JSON.parse(atob(b64));
        }
    </script>

</body>

</html>

Above, you can see there are two methods provided for the "discovery" process:

  1. beginTenantDiscoveryConcurrent
  2. beginTenantDiscoverySerial

When I use the first option ("beginTenantDiscoveryConcurrent"), I get the same token response for all handlers that complete within a threshold (below I am tracing the target tenant for which I initiated the token request, followed by the resulting "tid" claim in the token that MSAL sent to my .then callback):

Tenant azurestackci01.onmicrosoft.com: 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci02.onmicrosoft.com: 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci03.onmicrosoft.com: 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci04.onmicrosoft.com: 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci05.onmicrosoft.com: 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci06.onmicrosoft.com: 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci07.onmicrosoft.com: 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci08.onmicrosoft.com: 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci09.onmicrosoft.com: 104edf09-7fc1-459f-8c4e-b062db480b90

Above was for a case where the token for the first tenant was successful, and sent to all my .then handlers. As you can see and further investigate for yourself, it is the exact same token sent to all of them.

Below shows a case where one token call completed successfully and received a distinct response, but all the others received the incorrect error message (notice the error messages all have the same identifiers and time stamps on them):

Tenant azurestackci01.onmicrosoft.com: 104edf09-7fc1-459f-8c4e-b062db480b90

Tenant azurestackci02.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: a0650102-92e0-46af-be17-574e19284400
Correlation ID: ae870841-3854-48f7-a320-9561262f076c
Timestamp: 2020-01-20 23:30:20Z

Tenant azurestackci03.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: a0650102-92e0-46af-be17-574e19284400
Correlation ID: ae870841-3854-48f7-a320-9561262f076c
Timestamp: 2020-01-20 23:30:20Z

Tenant azurestackci04.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: a0650102-92e0-46af-be17-574e19284400
Correlation ID: ae870841-3854-48f7-a320-9561262f076c
Timestamp: 2020-01-20 23:30:20Z

Tenant azurestackci05.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: a0650102-92e0-46af-be17-574e19284400
Correlation ID: ae870841-3854-48f7-a320-9561262f076c
Timestamp: 2020-01-20 23:30:20Z

Tenant azurestackci06.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: a0650102-92e0-46af-be17-574e19284400
Correlation ID: ae870841-3854-48f7-a320-9561262f076c
Timestamp: 2020-01-20 23:30:20Z

Tenant azurestackci07.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: a0650102-92e0-46af-be17-574e19284400
Correlation ID: ae870841-3854-48f7-a320-9561262f076c
Timestamp: 2020-01-20 23:30:20Z

Tenant azurestackci08.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: a0650102-92e0-46af-be17-574e19284400
Correlation ID: ae870841-3854-48f7-a320-9561262f076c
Timestamp: 2020-01-20 23:30:20Z

Tenant azurestackci09.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: a0650102-92e0-46af-be17-574e19284400
Correlation ID: ae870841-3854-48f7-a320-9561262f076c
Timestamp: 2020-01-20 23:30:20Z

And for a final example, below shows when I use the second option ("beginTenantDiscoverySerial"), I get the expected output, with distinct error or success responses:

Tenant azurestackci01.onmicrosoft.com: 104edf09-7fc1-459f-8c4e-b062db480b90

Tenant azurestackci02.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: dda4cc54-59c7-4841-9ea4-3f32c96c3d00
Correlation ID: e6c57bd0-983b-43f4-bd17-6f7e7527afd1
Timestamp: 2020-01-20 23:37:26Z

Tenant azurestackci03.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: ae06fbd2-95f3-40e5-b140-244450724800
Correlation ID: 36213493-7ee7-4315-85ca-99a159411b23
Timestamp: 2020-01-20 23:37:26Z

Tenant azurestackci04.onmicrosoft.com: d669642b-89ec-466e-af2c-2ceab9fef685

Tenant azurestackci05.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: 09f38206-8d8a-4236-9dfb-435039113d00
Correlation ID: db99da0c-ed5b-4615-bdb1-26ec6d47c830
Timestamp: 2020-01-20 23:37:28Z

Tenant azurestackci06.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: f01f4e18-0013-4180-a05f-a579fa634400
Correlation ID: 41269eca-eee4-41a1-914f-2d1b130614d8
Timestamp: 2020-01-20 23:37:29Z

Tenant azurestackci07.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: 4dce60a8-3513-4cd9-9370-d1cfdbec4200
Correlation ID: d1615767-9223-4745-a1eb-8fda3adc2691
Timestamp: 2020-01-20 23:37:30Z

Tenant azurestackci08.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: 4ae77932-7ff9-4f12-8a1a-26912cfb3e00
Correlation ID: 9f34360d-9c0f-4ef8-b8f2-f4e89078c8b1
Timestamp: 2020-01-20 23:37:31Z

Tenant azurestackci09.onmicrosoft.com: AADSTS65001: The user or administrator has not consented to use the application with ID 'a963b646-fa01-4c06-b302-42c6ed1b2774' named 'Bryant's Demo App'. Send an interactive authorization request for this user and resource.
Trace ID: 57e4ed28-59b1-49ee-9cc4-c08f6b033900
Correlation ID: 5732e538-f25b-4af3-a65a-f6200bd01dcc
Timestamp: 2020-01-20 23:37:32Z

Expected behavior

I expect that the acquire token silent calls for each directory should have a distinct response or failure; if user doesn't exist in target tenant, or target tenant doesn't exist, or consent is required for the target tenant, I expect the corresponding error to be raised. If the token acquisition is successful, I expect to receive the access token for the target directory tenant with the target claims.

Browsers

Saw error in both IE11 and Chrome (latest version).

@keystroke keystroke added the bug A problem that needs to be fixed for the feature to function as intended. label Jan 20, 2020
@keystroke
Copy link
Author

As another data point, I added the "forceRefresh" request option to the acquire token silent request calls, and it did not help the issue:

Tenant azurestackci01.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci02.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci03.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci04.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci05.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci06.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci07.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci08.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
Tenant azurestackci09.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90

@keystroke
Copy link
Author

@sameerag tagging you here (hope its okay) to avoid reading above if this explanation is enough for you ;)

It seems that MSAL is creating iframes based only on target scope of token which needs to be acquired, instead of including target authority as well.

In above issue description, I show how acquiring multiple tokens concurrently does not work correctly, and will return duplicate data to the various callbacks in the promise. It seems (going from logs below) that the iframes created are identified only by target scope. Since the token requests I'm making for each directory are of the same scope, only one frame is created, though all the callbacks are registered, and thus they all get invoked with the result of that single frame. This also explains why switching to serial token acquisition works, as the frame is re-used each time with the correct token request properties.

I think that the iframes created by MSAL must be addressed by both target scopes and target authority (and perhaps you might identify other things to consider for multi-tenant scenarios).

Navigated to https://localhost:3000/

(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:07 GMT:1234-1.2.0-Info isCallback will be deprecated in favor of urlContainsHash in MSAL.js v2.0.
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:07 GMT:1234-1.2.0-Verbose Token is not in cache for scope:openid
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:07 GMT:1234-1.2.0-Verbose renewing accesstoken
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:07 GMT:1234-1.2.0-Verbose renewToken is called for scope:openid
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:07 GMT:1234-1.2.0-Info Add msal frame to document:msalRenewFrameopenid
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:07 GMT:1234-1.2.0-Verbose Renew token Expected state: d0bbeb71-4cf7-4faa-9184-9d4b1e483849
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:07 GMT:1234-1.2.0-Verbose Set loading state to pending for: openid:d0bbeb71-4cf7-4faa-9184-9d4b1e483849
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:07 GMT:1234-1.2.0-Info LoadFrame: msalRenewFrameopenid
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Info Add msal frame to document:msalRenewFrameopenid
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Info Returned from redirect url
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Info Processing the callback from redirect response
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Info State status:true; Request type:RENEW_TOKEN
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Info State is right
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Info Fragment has access token
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Info The user object received in the response is the same as the one passed in the acquireToken request
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose acquiring token interactive in progress

(index):80 Beginning tenant discovery concurrent: {uniqueId: "e21be0d4-2e8e-40d7-8e93-a15fcb868629", tenantId: "5454420b-2e38-4b9e-8b56-1712d321cf33", tokenType: "access_token", idToken: n, idTokenClaims: {…}, …}

(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Skipped cache lookup since request.forceRefresh option was set to true
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Skipped cache lookup since request.forceRefresh option was set to true
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Skipped cache lookup since request.forceRefresh option was set to true
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Skipped cache lookup since request.forceRefresh option was set to true
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Skipped cache lookup since request.forceRefresh option was set to true
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Skipped cache lookup since request.forceRefresh option was set to true
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Skipped cache lookup since request.forceRefresh option was set to true
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Skipped cache lookup since request.forceRefresh option was set to true
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Skipped cache lookup since request.forceRefresh option was set to true

(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose renewing accesstoken
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose renewToken is called for scope:openid
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Info Add msal frame to document:msalRenewFrameopenid
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Renew token Expected state: 796fd6cb-afcc-4f7f-bc82-54ecd2446632
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Set loading state to pending for: openid:796fd6cb-afcc-4f7f-bc82-54ecd2446632
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Info LoadFrame: msalRenewFrameopenid

(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Renew token for scope: openid is in progress. Registering callback
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Renew token for scope: openid is in progress. Registering callback
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Renew token for scope: openid is in progress. Registering callback
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Renew token for scope: openid is in progress. Registering callback
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Renew token for scope: openid is in progress. Registering callback
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Renew token for scope: openid is in progress. Registering callback
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Renew token for scope: openid is in progress. Registering callback
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Verbose Renew token for scope: openid is in progress. Registering callback

(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:08 GMT:1234-1.2.0-Info Add msal frame to document:msalRenewFrameopenid
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:09 GMT:1234-1.2.0-Info Returned from redirect url
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:09 GMT:1234-1.2.0-Info Processing the callback from redirect response
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:09 GMT:1234-1.2.0-Info State status:true; Request type:RENEW_TOKEN
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:09 GMT:1234-1.2.0-Info State is right
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:09 GMT:1234-1.2.0-Info Fragment has access token
(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:09 GMT:1234-1.2.0-Info The user object received in the response is the same as the one passed in the acquireToken request
(index):45 [MSAL] [3] Tue, 21 Jan 2020 01:44:09 GMT:1234-1.2.0-Verbose acquiring token interactive in progress

(index):88 Tenant azurestackci01.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
(index):88 Tenant azurestackci02.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
(index):88 Tenant azurestackci03.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
(index):88 Tenant azurestackci04.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
(index):88 Tenant azurestackci05.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
(index):88 Tenant azurestackci06.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
(index):88 Tenant azurestackci07.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
(index):88 Tenant azurestackci08.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90
(index):88 Tenant azurestackci09.onmicrosoft.com(fromCache: false): 104edf09-7fc1-459f-8c4e-b062db480b90

(index):42 [MSAL] [2] Tue, 21 Jan 2020 01:44:09 GMT:1234-1.2.0-Info isCallback will be deprecated in favor of urlContainsHash in MSAL.js v2.0.

@keystroke keystroke changed the title msal returns incorrect response when requesting multiple silent tokens concurrently MSAL fails to create separate hidden iframes for each token request based on target authority, resulting in returning the wrong token or error to registered callbacks Jan 21, 2020
@keystroke
Copy link
Author

I found a way to work around the issue for now... though it is obviously quite hacky.

In my previously-shared example, you can modify the token scopes to request 'openid' a duplicate number of times. This effectively "tricks" MSAL into creating unique hidden iframes for each target directory, as it uses the duplicated scopes in the identifier:

function beginTenantDiscoveryConcurrent(loginToken) {
    console.log('Beginning tenant discovery concurrent:', loginToken);
    for (let i = 1; i <= 9; i++) {
        let tenant = 'azurestackci0' + i + '.onmicrosoft.com';
        let disambiguation = '';
        for (let j = 0; j < i; j++) {
            disambiguation += ' openid'
        }
        msal.acquireTokenSilent({
            forceRefresh: true,
            scopes: ['openid', disambiguation],
            authority: 'https://login.microsoftonline.com/' + tenant
        }).then(function (token) {
            console.log('Tenant ' + tenant + '(fromCache: ' + token.fromCache + '):', getAccessTokenClaims(token).tid);
        }).catch(function (error) {
            console.log('Tenant ' + tenant + ':', error.message);
        });
    }
}

In below image, you can see we get unique resulting iframes:

image

It works only because AAD knows how to handle the duplicate scopes, and doesn't treat it as an error.

Note this isn't really a practical workaround to use generically, as you'd have to build a "queue" to wrap MSAL token methods and grab "openid scope strings" out of a queue to use with each token request, waiting until one becomes available. That might sound crazy to you, but you already have to do this if you use MSAL to acquire multiple tokens, some of which may require popups, as while a popup is running, any silent token requests can interfere with it, so when user code wants to initiate a popup request, you must drain all active silent token requests, enqueueing others if they come-in, and then proceed with the popup flow, after which you dequeue any other pending popup requests, followed by dequeueing any pending silent token requests. So this workaround is just an extra step to queue silent token requests based on the availability of these "openid scope strings", just as is done for popups, but you can have multiple active at once, instead of only a single popup.

@sameerag
Copy link
Member

@keystroke very interesting find and I think we have an issue to fix on the first look. Can I reach out once we figure out the best way to fix this?

@sameerag sameerag self-assigned this Jan 21, 2020
@keystroke
Copy link
Author

@sameerag thanks for quick response! I will happily test any new drops if you can provide them in a single js file which I can load with a script tag.

@sameerag
Copy link
Member

sameerag commented Feb 6, 2020

@keystroke can you pull the changes from #1225? I can share the compiled js offline if you are ready to test it

@keystroke
Copy link
Author

@sameerag Thanks! I can try this out today or tomorrow if you can provide the compiled JS (if you want to send via email or teams instead of posting here that is also fine).

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug A problem that needs to be fixed for the feature to function as intended.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants