PowerBI Embedded with a Service Principal Account

Throughout this month I’ve been working on our embedded PowerBI architecture – improving performance, streamlining administration, and reducing costs. We have a small PowerBI premium capacity that allows us to serve PowerBI reports and dashboards to internal and external users in our apps without individually provisioning licensing. In PowerBI embedded documentation, this is commonly referred to as the “app owns data” architecture. In this post, I will cover the implementation of a service principal for authentication and accessing the PowerBI embedded API.

Previously, PowerBI embedded in the “app owns data” architecture required the use of a master username/password combination when retrieving an embed token. Fortunately – a service principal can replace a master account in v2 workspaces and provides a pathway to a superior authentication implementation for “app owns data” scenarios.

App Owns Data PowerBI Embedded Architecture for Service Principal Authentication

Generate an Embed Token for Service Principal Authentication

The PowerBI JavaScript API for embedding reports/dashboards requires an embed token and report embed URL to be passed for the specific report. We obtain this information within a .Net Core Azure Function – to do so, we need 3 pieces of information:

  • PowerBI Workspace Id
  • Azure AD app registration – Application (client) Id and Client Secret
  • Report Id – can be sent at runtime

Workspace Id Visible in the Browser URL

Previous Architecture and Changes

PowerBI Workspace and Premium Capacity

The established PowerBI workspace needed to be upgraded from v1 to v2, which was nearly trivial and completed in place. If you are starting from scratch – you can create the PowerBI workspace from the PowerBI web interface and a PowerBI premium capacity from the Azure Portal.

Azure Active Directory App Registration

We already have an Azure AD app registered with the necessary permissions for the PowerBI API and will continue to use that same registered application with the addition of the service principal. To do so, we now need to generate a Client Secret in the application.

<p>
  The client secret, with the client/app id, is used to create the credential to authenticate our service principal with Azure AD.
</p>

<p>
  The &#8221; Application (client) ID&#8221; is found on the app registration overview tab. The client secrets are managed under &#8220;certificates & secrets.&#8221;
</p>
Azure AD App – Client Secret
string ADdomain = Environment.GetEnvironmentVariable("ADdomain");
string ClientId =  Environment.GetEnvironmentVariable("AppClientId"); 
string ClientSecret = Environment.GetEnvironmentVariable("AppClientSecret");

After grabbing those values from application settings, they are used with the ADAL.NET to authenticate against Azure AD. I wasn’t ready to jump to version 5 of ADAL.NET, so this example relies on 3.19.8.

var credential = new ClientCredential(ClientId, ClientSecret);

// Authenticate using created credentials
string AuthorityUrl = "https://login.microsoftonline.com/"+ADdomain+"/oauth2/v2.0/authorize";
var authenticationContext = new AuthenticationContext(AuthorityUrl);
var authenticationResult = await authenticationContext.AcquireTokenAsync(ResourceUrl, credential);

Creating a Service Principal

Creating a service principal and associating it with the Azure AD app can be accomplished through the Az Powershell module. It’s worth noting that when you start associating a service principal with an Azure AD app, previous permissions within Azure AD are terminated. This means that your prior integration may break if the permissions weren’t set within the PowerBI admin portal.

The Powershell script below takes your Azure AD app’s Application (Client) Id – as used above – then creates a service principal in that app (line 10), generates credentials for it, creates an Azure AD security group (line 16), and finally adds the service principal to the security group.

import-module Az

# Required to sign in as a tenant admin
Connect-AzAccount

# Create a new AAD web application
$app = Get-AzADApplication -ApplicationId 12345678-1234-1234-1234-123456789012

# Creates a service principal
$sp = New-AzADServicePrincipal -ApplicationId $app.ApplicationId -DisplayName "PowerBI_ServicePrincipal"

# Get the service principal key.
$key = New-AzADSpCredential -ObjectId $sp.ObjectId

# Create an AAD security group
$group = New-AzADGroup -DisplayName "PowerBI_Security" -MailNickName notSet

# Add the service principal to the group
Add-AzADGroupMember -TargetGroupObjectId $($group.ObjectId) -MemberObjectId $($sp.ObjectId)

Once the service principal and enclosing security group is created, you have 2 steps in the PowerBI admin portal to enable the service principal for the workspace:

Enable Service Principals

  1. Enable Service Principals in Tenant settings > Developer settings (scroll down) > Allow service principals to use Power BI APIs. For best security practices, limit this to the security group you created above.
  2. Add the security group as an admin of the PowerBI workspace – in the PowerBI portal within the workspace, select access in the upper right.

The Azure Function

In our architecture, an Azure Function takes a report Id as a query string for a report that has been published to the workspace. The function outputs the 3 key items necessary for embedding a report via the PowerBI JavaScript library: report Id, report embed URL, and an embed access token.

After the Azure AD authentication of the registered app, as above, the credentials are transformed for use by the PowerBI API. The authInfo object is a template for the return content.

var tokenCredentials = new TokenCredentials(authenticationResult.AccessToken, "Bearer");

// Create a Power BI Client object. It will be used to call Power BI APIs.
using (var client = new PowerBIClient(new Uri(ApiUrl), tokenCredentials))
{
    var report = await client.Reports.GetReportInGroupAsync(GroupId, reportInfo);

    // Generate Embed Token.
    var generateTokenRequestParameters = new GenerateTokenRequest(accessLevel: "view");
    var tokenResponse = await client.Reports.GenerateTokenInGroupAsync(GroupId, report.Id, generateTokenRequestParameters);

    if (tokenResponse == null)
    {
        log.LogInformation("Failed to generate embed token.");
    }

    authInfo.accessToken = (string)tokenResponse.Token;
    authInfo.embedUrl = (string)report.EmbedUrl;
    authInfo.embedReportId = (string)report.Id;
}

The resulting Azure Function can be called by our application to supply the embed token and embed URL for the report. For the full function and the PowerShell script, check out my example repository: https://github.com/dzsquared/powerbi-appownsdata-serviceprincipal

References

https://docs.microsoft.com/en-us/power-bi/developer/embed-service-principal
https://docs.microsoft.com/en-us/power-bi/developer/embed-sample-for-customers
https://docs.microsoft.com/en-us/powershell/module/az.resources/new-azadspcredential?view=azps-3.2.0
https://docs.microsoft.com/en-us/dotnet/api/microsoft.powerbi.api.v2.reportsextensions.getreportingroup?view=azure-dotnet
https://github.com/microsoft/PowerBI-Developer-Samples/tree/master/App%20Owns%20Data