Search code examples
c#azureazure-active-directorypowerbiadal

PowerBI and Azure AD Headless Login


I am trying to embed PowerBI dashboards into my customer MVC portal. My customers don't have AAD accounts, so they can't login to Live when they come to the website, they log into my MVC website with individual authority.

I have registered my App on PowerBI/AAD and have the ClientID and Secret. I make the call to AAD and get an Authorization Code which I then use to get an Athentication Token which the is returned successfully.

When ever I use the access token to get a dashboard it is continually rejected with a 403 Forbidden.

I have gone through all the samples from Microsoft, but they require a user login prompt. I have reviewed the ADAL2.0 code which refers to the AcquireToken Method, but this was deprecated in ADAL3 and replaced with AcquireTokenAsync which has different parameters and I am using this in my example below.

Here is the function to get the token:

    protected AuthenticationResult GetAccessToken()
    {
        string pBiUser = Properties.Settings.Default.PowerBIUser;
        string pBiPwd = Properties.Settings.Default.PowerBIPwd;
        string pBiClientId = Properties.Settings.Default.PowerBIClientId;
        string pBiSecret = Properties.Settings.Default.PowerBIClientSecret;
        TokenCache TC = new TokenCache();
        ClientCredential CC = new ClientCredential(pBiClientId,pBiSecret);
        string AU = Properties.Settings.Default.PowerBIAuthority;
        Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authenticationContext 
            = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(AU, TC);
        AuthenticationResult result = authenticationContext.AcquireTokenAsync("https://analysis.windows.net/powerbi/api"
            ,CC).Result;

        if (result == null)
        {
            throw new InvalidOperationException("Failed to obtain the PowerBI token");
        }

        return result;
    }

I then take the result token and call. The response receives the 403:

    protected PBIDashboards GetDashboards(AuthenticationResult authResult)
    {
        PBIDashboards pbiDashboards = new PBIDashboards();
        var baseAddress = new Uri("https://api.powerbi.com");
        using (var httpClient = new System.Net.Http.HttpClient {BaseAddress = baseAddress})
        {
            httpClient.DefaultRequestHeaders.TryAddWithoutValidation("authorization",
                "Bearer " + authResult.AccessToken);
            using (**var response** = httpClient.GetAsync("v1.0/myorg/dashboards").Result)
            {
                string responseData = response.Content.ReadAsStringAsync().Result;

                //Deserialize JSON string
                pbiDashboards = JsonConvert.DeserializeObject<PBIDashboards>(responseData);

                if (pbiDashboards != null)
                {
                    var gridViewDashboards = pbiDashboards.value.Select(dashboard => new
                    {
                        Id = dashboard.id,
                        DisplayName = dashboard.displayName,
                        EmbedUrl = dashboard.embedUrl
                    });
                }
            }
        }
        return pbiDashboards;
    }

Solution

  • After a lot of research, you can make a direct AJAX call to get the token:

        private async Task<string> GetAccessToken()
        {
            string pBiUser = Properties.Settings.Default.PowerBIUser;
            string pBiPwd = Properties.Settings.Default.PowerBIPwd;
            string pBiClientId = Properties.Settings.Default.PowerBIClientId;
            string pBiSecret = Properties.Settings.Default.PowerBIClientSecret;
            string pBITenant = Properties.Settings.Default.PowerBITenantId;
    
            string tokenEndpointUri = "https://login.microsoftonline.com/"+pBITenant+"/oauth2/token";
    
            var content = new FormUrlEncodedContent(new[]
                {
            new KeyValuePair<string, string>("grant_type", "password"),
            new KeyValuePair<string, string>("username", pBiUser),
            new KeyValuePair<string, string>("password", pBiPwd),
            new KeyValuePair<string, string>("client_id", pBiClientId),
            new KeyValuePair<string, string>("client_secret", pBiSecret),
            new KeyValuePair<string, string>("resource", "https://analysis.windows.net/powerbi/api")
            });
    
            using (var client = new HttpClient())
            {
                HttpResponseMessage res = client.PostAsync(tokenEndpointUri, content).Result;
    
                string json = await res.Content.ReadAsStringAsync();
    
                AzureAdTokenResponse tokenRes = JsonConvert.DeserializeObject<AzureAdTokenResponse>(json);
    
                return tokenRes.AccessToken;
            }
        }
    

    Once you have the string AccessToken, you can then call the Dashboards request.

    protected PBIDashboards GetDashboards(string token)
        {
            PBIDashboards pbiDashboards = new PBIDashboards();
            var baseAddress = new Uri("https://api.powerbi.com");
            using (var httpClient = new System.Net.Http.HttpClient {BaseAddress = baseAddress})
            {
                httpClient.DefaultRequestHeaders.TryAddWithoutValidation("authorization",
                    "Bearer " + token);
                using (var response = httpClient.GetAsync("v1.0/myorg/dashboards").Result)
                {
                    string responseData = response.Content.ReadAsStringAsync().Result;
    
                    //Deserialize JSON string
                    pbiDashboards = JsonConvert.DeserializeObject<PBIDashboards>(responseData);
    
                    if (pbiDashboards != null)
                    {
                        var gridViewDashboards = pbiDashboards.value.Select(dashboard => new
                        {
                            Id = dashboard.id,
                            DisplayName = dashboard.displayName,
                            EmbedUrl = dashboard.embedUrl
                        });
                    }
                }
            }
            return pbiDashboards;
        }
    

    This will provide you the list of dashboards and the dashboard Id to call the PowerBI API to build the embeded page in Javascript. I used hidden input fields to store the access token and embed URL to pass over to the Javascript call.

    // check if the embed url was selected
    var embedUrl = document.getElementById('embed').value;
    if (embedUrl === "")
        return;
    
    // get the access token.
    accessToken = document.getElementById('token').value;
    
    // Embed configuration used to describe the what and how to embed.
    // This object is used when calling powerbi.embed.
    // You can find more information at https://github.com/Microsoft/PowerBI-JavaScript/wiki/Embed-Configuration-Details.
    var config = {
        type: 'dashboard',
        accessToken: accessToken,
        embedUrl: embedUrl
    };
    
    // Grab the reference to the div HTML element that will host the dashboard.
    var dashboardContainer = document.getElementById('dashboard');
    
    // Embed the dashboard and display it within the div container.
    var dashboard = powerbi.embed(dashboardContainer, config);