AzureAD / azure-activedirectory-library-for-js

The code for ADAL.js and ADAL Angular has been moved to the MSAL.js repo. Please open any issues or PRs at the link below.
https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/maintenance/adal-angular
Apache License 2.0
627 stars 373 forks source link

Double login required before obtaining access token from Power BI Web API resource #845

Closed querylife closed 4 years ago

querylife commented 5 years ago

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[x] Other... Please describe: Double login required before obtaining access token from external Web API resource.

Browser:

Library Name

Library version

Library version: v1.0.17

Current behavior

Ultimately, I am trying to embed/render a Power BI report to a div from an Angular SPA, using the sample report found here (https://github.com/Azure-Samples/active-directory-angularjs-singlepageapp, "Integrating Azure AD into an AngularJS single page app"), as a starting point. I am able to successfully authenticate to Azure AD using an App Registration that has implicit grant/flow set to true. After successful login/authentication to the app, if I invoke the following ...

adalService.acquireToken('https://analysis.windows.net/powerbi/api');

In the Chrome debugger, I get these errors...

Error: No access token was found for element. You must specify an access token directly on the element using attribute 'powerbi-access-token' or specify a global token at: powerbi.accessToken.

Possibly unhandled rejection: Token renewal operation failed due to timeout|Token Renewal Failed

Also, the "adal.token.renew.statushttps://analysis.windows.net/powerbi/api" session var shows a value of "Cancelled"

However, if I use acquireTokenPopup instead of acquireToken, to the same Power BI API endpoint, the "adal.access.token.keyhttps://analysis.windows.net/powerbi/api" session variable is auto populated with an access token obtained from the "https://analysis.windows.net/powerbi/api" resource.

I am then able to successfully render the report using the powerbi.js powerbi-client v2.6.6 using the following code. Note: the accessToken property in the config var, that is passed to the power-bi client model, is set to the value of the "adal.token.renew.statushttps://analysis.windows.net/powerbi/api" session variable.

var embedUrl = "https://app.powerbi.com/reportEmbed"; var reportId1 = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";

var accessTokenSessionValue = $window.sessionStorage.getItem('adal.access.token.keyhttps://analysis.windows.net/powerbi/api');

var models1 = window['powerbi-client'].models; var config = { type: 'report', tokenType: models1.TokenType.Aad, // "User Owns Data/Embed for Org" scenario accessToken: accessTokenSessionValue, embedUrl: embedUrl, id: reportId1, viewMode: models1.ViewMode.View , permissions: models1.Permissions.Read }; // Embed the report within the div element // Embed report var $reportContainer1 = $('#embedDiv'); var report1 = powerbi.embed($reportContainer1.get(0), config);

//############################### app.js is using external API endpoints and popUp: true. Also, using requireADLogin: true on the route for the URL/page that renders the report.

var endpoints = {
    "https://analysis.windows.net/powerbi/api": "https://analysis.windows.net/powerbi/api",
    "https://api.powerbi.com": "https://analysis.windows.net/powerbi/api",
    "https://app.powerbi.com/reportEmbed": "https://app.powerbi.com/reportEmbed"
};

adalProvider.init(
//window.config =(
    {
        instance: 'https://login.microsoftonline.com/', 
        tenant: 'MyTenantName (such as contoso.com)',
        clientId: 'AppIdOfAzureAppRegWithImplicitFlowSetTrue', 
        popUp: true, // intentionally have this enabled to avoid default redirect auth behavior because need auth to work in iframe from Sitecore CMS
        endpoints: endpoints
        //cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
        ,cacheLocation: 'sessionStorage', // enable this for IE, as sessionStorage does not work for localhost.
        //postLogoutRedirectUri: window.location.origin            
    }
    ,$httpProvider // pass http provider to inject request interceptor to attach tokens
);

$sceDelegateProvider.resourceUrlWhitelist([
    'self',
    'https://*.powerbi.com/**'
]);

Expected behavior

I would expect to not have to initially be prompted for login twice. I am first being prompted to sign into the app after clicking login and then when I access the page where the report renders, it forces me to login again to obtain a token for the power bi resource.

Chrome debugger shows a valid token for the adal.access.token.keyXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX session var (where the X's correspond to the client/app ID) and a second valid token, that's obtained by the second login prompt, is saved in the "adal.access.token.keyhttps://analysis.windows.net/powerbi/api" session var.

Minimal reproduction of the problem with instructions

  1. Click Login link in app nav bar, prompted for login to MS Azure, enter credentials for tenant where app is registered. Successfully logged in with no errors in Chrome debugger. Able to click the User link in nav bar and successfully showing Id_token content such as upn and aud user properties.
  2. Click Reports link, browser debugger shows: "Error: No access token was found for element. You must specify an access token directly on the element using attribute 'powerbi-access-token' or specify a global token at: powerbi.accessToken." Prompted a second time for credentials - no errors in debugger, but power bi report won't render until I reload the page. Each time I reload the page, I am prompted for credentials. If I cancel the login prompt popup, the report does render successfully.

querylife commented 5 years ago

For this issue I submitted about 2 hours ago, I want to clarify that I am able to get the plain Javascript SPA ADAL.js sample (found here https://github.com/Azure-Samples/active-directory-javascript-singlepageapp-dotnet-webapi) working successfully without a double login prompt. This plain JS example does not use AngularJS. In the plain JS sample, I am using the same config and endpoints settings (with authContext) as I was using with adalProvider.init in the Angular sample.

This code snippet from the plain JS sample using ADAL.js is successfully obtaining an access token from the Power BI API and I am able to use it to render reports using powerbi.js without issues.

var authContext = new AuthenticationContext(config);

var user = authContext.getCachedUser();

authContext.acquireToken('https://analysis.windows.net/powerbi/api', function (error, token) { console.log("ERROR: " + error); console.log("TOKEN: " + token);

        accessToken = token;

            if (!!error) {
            console.log(error.indexOf("interaction_required"));

            authContext.acquireTokenPopup(
                'https://analysis.windows.net/powerbi/api',
                null,
                null,
                function (error, token) {
                    console.log(error);
                    console.log(token);
                }
            )
            }

    });

I don't understand why acquireToken and acquireTokenPopup is prompting for credentials when I try to obtain a token from the Power BI API endpoint when I am already successfully authenticated to Azure AD using oauth v1.

querylife commented 5 years ago

I worked on this issue more today. The goal is to be able to obtain an access token from the Power BI API without getting prompted for oauth authentication twice. I examined the plain JS sample (noted earlier in this posting) and found that I was able to use this code ... authContext.acquireToken( 'https://analysis.windows.net/powerbi/api', function (error, token) {

                    if (error || !token) {
                        // TODO: Handle error obtaining access token
                        document.getElementById('api_response').textContent =
                            'ERROR:\n\n' + error;
                        return;
                    }

to obtain an access token for power bi after confirming that the user is logged in by evaluating whether or not authContext.getCachedUser(); is true.

Now, I need to go back to the Angular sample code (also noted earlier in this posting) and see what the equivalent way is to evaluating whether the user is logged in or not. I would expect that adalAuthenticationService.acquireToken (using the adal-angular.js library) would be able to return a token as long as the user is logged in. At that point, if it is found that the user is not logged in, then I would think that adalAuthenticationService.acquireTokenPopup could be invoked.

The question becomes, what is the equivalent to the adal.js' authContext.getCachedUser() function in the adal-angular.js library?

querylife commented 5 years ago

I'm back at it trying to get the AngularJS sample code (named "An AngularJS SPA authenticating users with Azure AD and calling its own backend web API", located here https://github.com/Azure-Samples/active-directory-angularjs-singlepageapp) working to generate an access token for a Power BI API resource. I enabled verbose logging (level 3) for adal.js v1.0.17 and it has helped a bit.

For this scenario, I am already successfully authenticating to AAD after logging into the app. Next, I make the following aquireToken call using adalService which maps to the adalAuthenticationServiceProvider.

adalService.acquireToken('https://analysis.windows.net/powerbi/api', function (error, token) {

    // Handle ADAL Error
    if (error || !token) {
        printErrorMessage('ADAL Error Occurred: ' + error);
        console.log('ADAL Error Occurred: ' + error);
        return;
    }
});

The above code is invoked by accessing this page https://localhost:44326/#!/Report

The verbose browser console logging shows the following. Note: my domain has been intentionally replaced by contoso.com and GuIDs have been replaced with all X's for security reasons.

Mon, 07 Jan 2019 23:10:57 GMT:1.0.17-VERBOSE: Url: /App/Views/Report.html maps to resource: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Mon, 07 Jan 2019 23:10:57 GMT:1.0.17-INFO: Token is available for this url /App/Views/Report.html Mon, 07 Jan 2019 23:11:18 GMT:1.0.17-VERBOSE: renewing access_token Mon, 07 Jan 2019 23:11:18 GMT:1.0.17-INFO: renewToken is called for resource:https://analysis.windows.net/powerbi/api Mon, 07 Jan 2019 23:11:18 GMT:1.0.17-INFO: Add adal frame to document:adalRenewFramehttps://analysis.windows.net/powerbi/api Mon, 07 Jan 2019 23:11:18 GMT:1.0.17-VERBOSE: Renew token Expected state: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX|https://analysis.windows.net/powerbi/api Mon, 07 Jan 2019 23:11:18 GMT:1.0.17-INFO: Navigate url:https://login.microsoftonline.com/consoso.com/oauth2/authorize?response_type=token&client_id=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&resource=https%3A%2F%2Fanalysis.windows.net%2Fpowerbi%2Fapi&redirect_uri=https%3A%2F%2Flocalhost%3A44326%2F&state=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX%7Chttps%3A%2F%2Fanalysis.windows.net%2Fpowerbi%2Fapi&client-request-id=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&x-client-SKU=Js&x-client-Ver=1.0.17 Mon, 07 Jan 2019 23:11:18 GMT:1.0.17-VERBOSE: Set loading state to pending for: https://analysis.windows.net/powerbi/api Mon, 07 Jan 2019 23:11:18 GMT:1.0.17-INFO: LoadFrame: adalRenewFramehttps://analysis.windows.net/powerbi/api

The end result is that the value of token (in the acquireToken callback function) ends up being a blank string and never ends up with an actual access token value in it.

Next, the verbose logging shows this ...

Mon, 07 Jan 2019 23:11:18 GMT:1.0.17-INFO: Add adal frame to document:adalRenewFramehttps://analysis.windows.net/powerbi/api Mon, 07 Jan 2019 23:11:18 GMT:1.0.17-INFO: LoadFrame: adalRenewFramehttps://analysis.windows.net/powerbi/api Mon, 07 Jan 2019 23:11:19 GMT:1.0.17-INFO: Add adal frame to document:adalRenewFramehttps://analysis.windows.net/powerbi/api Mon, 07 Jan 2019 23:11:19 GMT:1.0.17-VERBOSE: Location change event from https://localhost:44326/#!#access_token=eyJ0e......557LENg&token_type=Bearer&expires_in=3600&state=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX%7Chttps:%2F%2Fanalysis.windows.net%2Fpowerbi%2Fapi&session_state=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX to https://localhost:44326/#!#access_token=eyJ0e......557LENg&token_type=Bearer&expires_in=3600&state=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX%7Chttps:%2F%2Fanalysis.windows.net%2Fpowerbi%2Fapi&session_state=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Mon, 07 Jan 2019 23:11:19 GMT:1.0.17-VERBOSE: Url: /App/Views/Home.html maps to resource: null Mon, 07 Jan 2019 23:11:24 GMT:1.0.17-VERBOSE: Loading frame has timed out after: 6 seconds for resource https://analysis.windows.net/powerbi/api

At this point, it appears that I am getting a valid access token (response) showing up in the URL because if I take the value of the access_token query parameter and hard code it into the powerbi.embed function config parameters, I can successfully render an embedded power bi report until the access token expires.

I do not understand why the following aquireToken callback function is not extracting/obtaining the access_token value from the access_token query parameter into the token variable and is instead coming back with a blank string value.

adalService.acquireToken('https://analysis.windows.net/powerbi/api', function (error, token) {

    // Handle ADAL Error
    if (error || !token) {
        printErrorMessage('ADAL Error Occurred: ' + error);
        console.log('ADAL Error Occurred: ' + error);
        return;
    }
});

Why should I have to come up with code to extract the access_token value from the access_token query parameter in the response? Shouldn't this be handled automatically by adalService.acquireToken?

querylife commented 5 years ago

Below is more verbose log output.

The following is what happens after successful Login (after clicking the Login button for the app)... Tue, 08 Jan 2019 15:44:55 GMT:1.0.15-VERBOSE: Location change event from https://localhost:44326/ to https://localhost:44326/ Tue, 08 Jan 2019 15:44:55 GMT:1.0.15-VERBOSE: Location change event from https://localhost:44326/ to https://localhost:44326/#!/Home Tue, 08 Jan 2019 15:45:03 GMT:1.0.15-VERBOSE: Expected state: b42d6210-XXXX-XXXX-XXXX-fe9b043d3192 startPage:https://localhost:44326/#!/Home Tue, 08 Jan 2019 15:45:03 GMT:1.0.15-INFO: Navigate url:https://login.microsoftonline.com/contoso.com/oauth2/authorize?response_type=id_token&client_id=111382d1-XXXX-XXXX-XXXX-865c01bcf397&redirect_uri=https%3A%2F%2Flocalhost%3A44326%2F&state=b42d6210-XXXX-XXXX-XXXX-fe9b043d3192&client-request-id=c27fdf61-XXXX-XXXX-XXXX-31de87677bbc&x-client-SKU=Js&x-client-Ver=1.0.15 Tue, 08 Jan 2019 15:45:13 GMT:1.0.15-VERBOSE: Processing the hash: #id_token=eyJ0e......8aMgw&state=b42d6210-e013-47a5-8ca4-fe9b043d3192&session_state=cf8c0b58-a36e-4f21-91db-e1cc6661ce5d Tue, 08 Jan 2019 15:45:13 GMT:1.0.15-VERBOSE: State: b42d6210-e013-47a5-8ca4-fe9b043d3192 Tue, 08 Jan 2019 15:45:13 GMT:1.0.15-INFO: State status:true; Request type:LOGIN Tue, 08 Jan 2019 15:45:13 GMT:1.0.15-INFO: State is right Tue, 08 Jan 2019 15:45:13 GMT:1.0.15-INFO: Fragment has id token Tue, 08 Jan 2019 15:45:13 GMT:1.0.15-INFO: Closing popup window

Here I intentionally clicked on "User" link in the nav bar and successfully displayed user info... Tue, 08 Jan 2019 15:45:18 GMT:1.0.15-VERBOSE: Location change event from https://localhost:44326/#!/Home to https://localhost:44326/#!/UserData

Here I intentionally clicked on "Report" link in the nav bar. The Report page has the code to aquireToken... Tue, 08 Jan 2019 15:45:23 GMT:1.0.15-VERBOSE: Location change event from https://localhost:44326/#!/UserData to https://localhost:44326/#!/Report Tue, 08 Jan 2019 15:45:23 GMT:1.0.15-INFO: renewToken is called for resource:https://analysis.windows.net/powerbi/api Tue, 08 Jan 2019 15:45:23 GMT:1.0.15-INFO: Add adal frame to document:adalRenewFramehttps://analysis.windows.net/powerbi/api Tue, 08 Jan 2019 15:45:23 GMT:1.0.15-VERBOSE: Renew token Expected state: 51028b19-XXXX-XXXX-a2b2-3817336e0a2f|https://analysis.windows.net/powerbi/api Tue, 08 Jan 2019 15:45:23 GMT:1.0.15-INFO: Navigate url:https://login.microsoftonline.com/contoso.com/oauth2/authorize?response_type=token&client_id=111382d1-XXXX-XXXX-a0a9-865c01bcf397&resource=https%3A%2F%2Fanalysis.windows.net%2Fpowerbi%2Fapi&redirect_uri=https%3A%2F%2Flocalhost%3A44326%2F&state=51028b19-XXXX-XXXX-XXXX-3817336e0a2f%7Chttps%3A%2F%2Fanalysis.windows.net%2Fpowerbi%2Fapi&client-request-id=8d2d186b-XXXX-XXXX-XXXX-5e6842cff501&x-client-SKU=Js&x-client-Ver=1.0.15 Tue, 08 Jan 2019 15:45:23 GMT:1.0.15-VERBOSE: Navigate to:https://login.microsoftonline.com/contoso.com/oauth2/authorize?response_type=token&client_id=111382d1-XXXX-XXXX-a0a9-865c01bcf397&resource=https%3A%2F%2Fanalysis.windows.net%2Fpowerbi%2Fapi&redirect_uri=https%3A%2F%2Flocalhost%3A44326%2F&state=51028b19-5572-4ee2-a2b2-3817336e0a2f%7Chttps%3A%2F%2Fanalysis.windows.net%2Fpowerbi%2Fapi&client-request-id=8d2d186b-XXXX-XXXX-XXXX-5e6842cff501&x-client-SKU=Js&x-client-Ver=1.0.15&prompt=none&login_hint=admin%40contoso.onmicrosoft.com&domain_hint=contoso.onmicrosoft.com Tue, 08 Jan 2019 15:45:23 GMT:1.0.15-VERBOSE: Set loading state to pending for: https://analysis.windows.net/powerbi/api Tue, 08 Jan 2019 15:45:23 GMT:1.0.15-INFO: LoadFrame: adalRenewFramehttps://analysis.windows.net/powerbi/api Tue, 08 Jan 2019 15:45:24 GMT:1.0.15-INFO: Add adal frame to document:adalRenewFramehttps://analysis.windows.net/powerbi/api Tue, 08 Jan 2019 15:45:24 GMT:1.0.15-INFO: LoadFrame: adalRenewFramehttps://analysis.windows.net/powerbi/api Tue, 08 Jan 2019 15:45:24 GMT:1.0.15-VERBOSE: Location change event FROM https://localhost:44326/#!#access_token=eyJ0e......5CieT-A&token_type=Bearer&expires_in=3599&state=51028b19-XXXX-XXXX-XXXX-3817336e0a2f%7Chttps:%2F%2Fanalysis.windows.net%2Fpowerbi%2Fapi&session_state=cf8c0b58-XXXX-XXXX-XXXX-e1cc6661ce5d TO https://localhost:44326/#!#access_token=eyJ0e......5CieT-A&token_type=Bearer&expires_in=3599&state=51028b19-XXXX-XXXX-XXXX-3817336e0a2f%7Chttps:%2F%2Fanalysis.windows.net%2Fpowerbi%2Fapi&session_state=cf8c0b58-XXXX-XXXX-XXXX-e1cc6661ce5d Tue, 08 Jan 2019 15:45:24 GMT:1.0.15-VERBOSE: Location change event FROM https://localhost:44326/#!#access_token=eyJ0e......5CieT-A&token_type=Bearer&expires_in=3599&state=51028b19-XXXX-XXXX-XXXX-3817336e0a2f%7Chttps:%2F%2Fanalysis.windows.net%2Fpowerbi%2Fapi&session_state=cf8c0b58-XXXX-XXXX-XXXX-e1cc6661ce5d TO https://localhost:44326/#!/Home#access_token=eyJ0e......5CieT-A&token_type=Bearer&expires_in=3599&state=51028b19-XXXX-XXXX-XXXX-3817336e0a2f%7Chttps:%2F%2Fanalysis.windows.net%2Fpowerbi%2Fapi&session_state=cf8c0b58-XXXX-XXXX-XXXX-e1cc6661ce5d Tue, 08 Jan 2019 15:45:25 GMT:1.0.15-INFO: Add adal frame to document:adalRenewFramehttps://analysis.windows.net/powerbi/api Tue, 08 Jan 2019 15:45:29 GMT:1.0.15-VERBOSE: Loading frame has timed out after: 6 seconds for resource https://analysis.windows.net/powerbi/api

Tue, 08 Jan 2019 15:45:29 GMT:1.0.15-ERROR: Error when acquiring token for resource: https://analysis.windows.net/powerbi/api stack: undefined Possibly unhandled rejection: Token renewal operation failed due to timeout|Token Renewal Failed

==========

Ultimately, LoadFrame: adalRenewFramehttps://analysis.windows.net/powerbi/api is giving a timeout error after 6 sec, when using adal-angular.js and adal.js v1.0.15, but even when I use v1.0.17 and set loadFrameTimeout to 10 or more seconds in the adalProvider.init, it still gives the timeout error. However, the verbose location change event is showing that the URL that is changed to, does have a valid access_token parameter value with token_type=Bearer and expires_in=3599 (seconds).

It appears to me that there is a bug in aquireToken for the AngularJS wrapper where the callback function below is not able to grab the error nor the token properly from the aquireToken function.

adalService.acquireToken('https://analysis.windows.net/powerbi/api', function (error, token) {

// Handle ADAL Error
if (error || !token) {
    printErrorMessage('ADAL Error Occurred: ' + error);
    console.log('ADAL Error Occurred: ' + error);
    return;
}

});

How can I obtain support for this issue as I nobody is responding to this issue thus far? This is super frustrating. Others have not been able to get acquireToken working with Angular, but nobody seems to care or want to resolve this. With the pure adal.js library, I can make aquireToken work okay with an external resource to power bi api, but the page/URL routing in a plain JS SPA is not desirable. The community really needs a working sample of an AngularJS SPA that uses acquireToken to call an external API such as Power BI. My guess is that if you actually tried to make this work, you would see that something is wrong with the AngularJS ADAL wrapper acquireToken code - it just doesn't work as expected. I have spent way too much time trying to get this to work. I challenge the community to prove me wrong and show that there is not a bug with the AngularJS ADAL wrapper code.

mohandivraniya commented 5 years ago

I have seen this behavior in my application as well. However, I am not able to reproduce it consistently. Sometimes the token is received and sometimes not. I am yet to figure out the root cause for it.

querylife commented 5 years ago

mohandivraniya - thanks for the response. I have given up on this.

jacks0n commented 5 years ago

I also ran into this issue, when trying to authenticate Power BI silently after logging in. What worked for me was requesting the Power BI permissions and user_impersonation on login, then silently requesting the Power BI access token after login.

jmckennon commented 4 years ago

This bug is fixed in msal js. All current authentication work from Microsoft is delivered through the msal js library here. adal js is still supported only for security fixes. We recommend moving to msal js for any advanced feature requests and bugfixes.