Power BI Advanced Series · Power BI Embedding & Security · by Raushan Ranjan, MCT
In our previous post, we discussed the concepts behind the "App Owns Data" Power BI embedding model. Now, let's put those concepts into practice with a step-by-step tutorial. We'll build a simple ASP.NET MVC application that embeds a Power BI report and allows for dynamic filtering.
1. Prerequisites
Before we begin, ensure you have the following:
- A Power BI Pro or Premium account (to publish reports).
- An Azure AD tenant where you can register an app.
- Visual Studio 2019 or later with .NET Core installed.
- A basic understanding of ASP.NET MVC.
- A Power BI (.pbix) report ready to be published. For this tutorial, we will assume you have a simple report based on a sales dataset.
2. Step 1: Register an Azure AD App
The first step is to create an identity for your application in Azure.
- Go to the Azure Portal and navigate to Microsoft Entra ID (Azure AD).
- Select App registrations from the left-hand menu, then click New registration.
- Fill in the details:
- Name: `PowerBIAppEmbed` (or any name you prefer).
- Supported account types: Select Accounts in this organizational directory only (Single tenant).
- After registration, note down the Application (client) ID and Directory (tenant) ID. You'll need these later.
- Navigate to Certificates & secrets and create a new Client Secret. Copy its value immediately, as you won't be able to see it again.
- Go to API permissions and click Add a permission. Select the Power BI Service and add the following Application permissions:
- `Tenant.Read.All` (or `Tenant.ReadWrite.All`)
- Finally, click Grant admin consent for [Your Tenant Name].
✅ This process gives your app the credentials and permissions it needs to programmatically access Power BI APIs.
3. Step 2: Configure Power BI Workspace
Now, let's configure the Power BI side to allow our app to access the report.
- Go to the Power BI Service and navigate to the workspace where you want to embed the report.
- Go to Workspace → Manage Access.
- Add your registered app (the Service Principal) as an Admin or Member. You can search for it by the name you gave it in Step 1.
- Publish your PBIX report (e.g., `BeverageSalesReport`) to this workspace.
Important: Note down the Workspace ID and Report ID from the URL of the report in the Power BI Service. The URL will look something like this: `https://app.powerbi.com/groups/<WorkspaceID>/reports/<ReportID>`.
4. Step 3: Backend Setup – PowerBIService.cs
We'll create a C# service that handles the authentication with Azure AD and the generation of the embed token.
using System;
using System.Collections.Generic;
using Microsoft.PowerBI.Api;
using Microsoft.PowerBI.Api.Models;
using Microsoft.Rest;
using Microsoft.Identity.Client;
using System.Linq;
namespace MVC_Application.Services
{
// A class to hold the embed configuration returned to the frontend
public class EmbedConfig
{
public string EmbedToken { get; set; }
public string EmbedUrl { get; set; }
public string ReportId { get; set; }
}
public class PowerBIService
{
private readonly string clientId;
private readonly string clientSecret;
private readonly string tenantId;
private readonly string workspaceId;
private readonly string reportId;
// Constructor to inject credentials
public PowerBIService(string clientId, string clientSecret, string tenantId, string workspaceId, string reportId)
{
this.clientId = clientId;
this.clientSecret = clientSecret;
this.tenantId = tenantId;
this.workspaceId = workspaceId;
this.reportId = reportId;
}
public EmbedConfig GetEmbedConfig()
{
// Define the scope for accessing Power BI APIs
string[] scopes = new string[] { "https://analysis.windows.net/powerbi/api/.default" };
// Acquire token from Azure AD using client credentials flow
var clientApp = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri($"https://login.microsoftonline.com/{tenantId}"))
.Build();
var authResult = clientApp.AcquireTokenForClient(scopes).ExecuteAsync().Result;
// Use the access token to create a Power BI client
var tokenCredentials = new TokenCredentials(authResult.AccessToken, "Bearer");
using (var pbiClient = new PowerBIClient(new Uri("https://api.powerbi.com/"), tokenCredentials))
{
var workspaceGuid = Guid.Parse(workspaceId);
var reportGuid = Guid.Parse(reportId);
// Get report information
var report = pbiClient.Reports.GetReportInGroup(workspaceGuid, reportGuid);
// Generate a short-lived embed token for the report
var tokenRequest = new GenerateTokenRequestV2(
reports: new List<GenerateTokenRequestV2Report>
{
new GenerateTokenRequestV2Report(report.Id)
},
datasets: new List<GenerateTokenRequestV2Dataset>
{
new GenerateTokenRequestV2Dataset(report.DatasetId)
},
targetWorkspaces: new List<GenerateTokenRequestV2TargetWorkspace>
{
new GenerateTokenRequestV2TargetWorkspace(workspaceGuid)
}
);
var embedToken = pbiClient.EmbedToken.GenerateToken(tokenRequest);
// Return the necessary information for the frontend
return new EmbedConfig
{
EmbedToken = embedToken.Token,
EmbedUrl = report.EmbedUrl,
ReportId = report.Id.ToString()
};
}
}
}
}
Explanation: This service handles the two main backend tasks: authenticating with Azure AD and generating the embed token using the Power BI REST API.
5. Step 4: MVC Controller – HomeController.cs
This controller will handle the web requests, fetch the embed configuration from our service, and pass it to the view.
using Microsoft.AspNetCore.Mvc;
using MVC_Application.Services;
using System.Collections.Generic;
namespace MVC_Application.Controllers
{
public class HomeController : Controller
{
private readonly PowerBIService _pbiService;
// Dependency Injection to get an instance of our service
public HomeController(PowerBIService pbiService)
{
_pbiService = pbiService;
}
// GET: Displays the report page with default filters
[HttpGet]
public IActionResult ReportDisplay()
{
// Dummy data for filter dropdowns
ViewBag.Brands = new List<string> { "Coca-Cola", "Diet Coke", "Sprite", "Fanta", "Powerade", "Dasani Water" };
ViewBag.Retailers = new List<string> { "Walmart", "Costco", "CVS", "Target" };
ViewBag.Regions = new List<string> { "Northeast", "Southeast", "Midwest", "West", "South" };
var embedConfig = _pbiService.GetEmbedConfig();
ViewBag.EmbedConfig = embedConfig;
return View();
}
// POST: Reloads the page with selected filters applied
[HttpPost]
public IActionResult ReportDisplay(string selectedBrand, string selectedRetailer, string selectedRegion)
{
// Repopulate filter lists
ViewBag.Brands = new List<string> { "Coca-Cola", "Diet Coke", "Sprite", "Fanta", "Powerade", "Dasani Water" };
ViewBag.Retailers = new List<string> { "Walmart", "Costco", "CVS", "" };
ViewBag.Regions = new List<string> { "Northeast", "Southeast", "Midwest", "West", "South" };
// Store selected filter values
ViewBag.SelectedBrand = selectedBrand;
ViewBag.SelectedRetailer = selectedRetailer;
ViewBag.SelectedRegion = selectedRegion;
var embedConfig = _pbiService.GetEmbedConfig();
ViewBag.EmbedConfig = embedConfig;
return View();
}
}
}
6. Step 5: MVC View – ReportDisplay.cshtml
This is the front-end code that will load the Power BI report using the JavaScript SDK and apply the filters.
<form asp-action="ReportDisplay" method="post" class="row g-3 mb-4">
<div class="col-md-4">
<label class="form-label fw-bold">Beverage Brand</label>
<select class="form-select" name="selectedBrand">
<option value="">-- Select Brand --</option>
@foreach (var choice in ViewBag.Brands as List<string>)
{
<option value="@choice" selected="@(choice == ViewBag.SelectedBrand ? "selected" : null)">
@choice
</option>
}
</select>
</div>
<!-- Repeat similar blocks for Retailer and Region -->
<div class="col-12 d-flex justify-content-end mt-3">
<button type="submit" class="btn btn-primary btn-lg">Load Report</button>
</div>
</form>
<div id="reportContainer" style="height:600px; width:100%;"></div>
@section Scripts {
<!-- Power BI JavaScript SDK -->
<script src="https://cdn.jsdelivr.net/npm/powerbi-client/dist/powerbi.min.js"></script>
<script>
var models = window['powerbi-client'].models;
// Configuration for embedding the report
var embedConfig = {
type: 'report',
tokenType: models.TokenType.Embed,
accessToken: "@ViewBag.EmbedConfig.EmbedToken",
embedUrl: "@ViewBag.EmbedConfig.EmbedUrl",
id: "@ViewBag.EmbedConfig.ReportId",
settings: { filterPaneEnabled: false, navContentPaneEnabled: true }
};
// Get the report container and embed the report
var report = powerbi.embed(document.getElementById('reportContainer'), embedConfig);
// Apply filters dynamically based on user selection
var filters = [];
if ('@(ViewBag.SelectedBrand ?? "")') {
filters.push({
$schema: "http://powerbi.com/product/schema#basic",
target: { table: "Data", column: "Beverage Brand" },
filterType: models.FilterType.Basic,
operator: "In",
values: ['@(ViewBag.SelectedBrand)']
});
}
// When the report is loaded, apply the filters
report.on("loaded", function () {
if (filters.length) {
report.setFilters(filters);
}
});
</script>
}
Explanation: This view loads the Power BI JavaScript SDK and uses the `embedConfig` passed from the controller to render the report. The JavaScript code then checks for selected filters and applies them to the report.
7. Step 6: Configure Startup.cs
Finally, we need to register our `PowerBIService` in `Startup.cs` so that it can be used via dependency injection in our controller.
public void ConfigureServices(IServiceCollection services)
{
// Register our Power BI service with app credentials
services.AddSingleton<PowerBIService>(sp =>
new PowerBIService(
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
tenantId: "YOUR_TENANT_ID",
workspaceId: "YOUR_WORKSPACE_ID",
reportId: "YOUR_REPORT_ID"
)
);
// Add other services here
services.AddControllersWithViews();
}
Explanation: Remember to replace the placeholder values with the actual IDs and secrets you noted down in the earlier steps. This makes `PowerBIService` available to any class that requests it in its constructor.
8. Step 7: Test Your Application
With everything configured, you can now run your MVC application and navigate to the report page.
- Run your MVC app.
- Navigate to `/Home/ReportDisplay`.
- You should see the embedded Power BI report.
- Use the dropdowns to select a Brand, Retailer, or Region, and click "Load Report".
- The report will refresh, and you should see the data filtered by your selections.
✅ Congratulations! You now have a fully working "App Owns Data" embedding setup.
9. Next Steps & Enhancements
- Implement Row-Level Security (RLS): Modify the `PowerBIService` to add a user identity or role to the `GenerateTokenRequest` to apply RLS.
- Embed Multiple Reports: Change the `GetEmbedConfig` method to handle a list of reports.
- Dynamic Filters: Load the filter dropdowns dynamically from the Power BI dataset instead of hardcoding the values.
- Secure Secrets: Instead of hardcoding credentials in `Startup.cs`, use a secure method like Azure Key Vault.
📚 Continue Learning — PL-300 on RR Skillverse
Quick Knowledge Check
Q1. What is the correct sequence for generating and using an embed token in an app-owns-data scenario?
Show Answer
1. Register an Azure AD app and grant Power BI API permissions. 2. Authenticate server-side to get an Azure AD access token. 3. Call the Power BI REST API to generate an embed token for the specific report. 4. Pass the embed token and embed URL to the client. 5. Client uses the Power BI JavaScript SDK to render the report in a div. The Azure AD access token stays server-side. Only the embed token (short-lived, report-specific) is sent to the browser.
Q2. In the Power BI JavaScript SDK, what does powerbi.embed(container, config) require in the config object?
- A) The service principal's client secret and tenant ID
- B) The embed URL (from the Power BI REST API), the embed token, and the report type
- C) The report's DAX queries and data model connection string
- D) The user's Azure AD credentials and Power BI workspace URL
Show Answer
B. The embed config requires: type: "report", id (report GUID), embedUrl (from the Power BI REST API response), accessToken (the embed token, NOT an Azure AD token), and optionally settings for filter pane/navigation visibility. The service principal credentials must never reach the client — they stay in the backend.
Q3. An embedded report works in development but shows "You don't have permission to view this report" in production. What are the two most likely causes?
Show Answer
1. The service principal is not a member of the Power BI workspace containing the report. Service principals must be explicitly added to the workspace (as Member or higher) to generate embed tokens for reports in that workspace. 2. The "Allow service principals to use Power BI APIs" toggle is not enabled in the Power BI Admin portal. Without this setting, service principals cannot call Power BI REST APIs regardless of Azure AD permissions.
5 Things to Remember
- Azure AD access token stays server-side — use it to call Power BI REST API and generate an embed token. Only the embed token goes to the browser.
- Service principal must be in the workspace — add it as a Member in Power BI Service. Also enable "Allow service principals to use Power BI APIs" in the Admin portal.
- Embed token includes the report ID and access level — one embed token = one report, one access level (view or edit), one optional effective identity for RLS.
- Power BI JavaScript SDK renders client-side —
powerbi.embed(div, config) with embedUrl and accessToken (embed token) renders the report inside a container div.
- Refresh embed tokens before expiry — embed tokens expire (typically 1 hour). Implement a token refresh call in your app to avoid session drops on long-running reports.
Quick Knowledge Check
Q1. What is the correct sequence for generating and using an embed token in an app-owns-data scenario?
Show Answer
1. Register an Azure AD app and grant Power BI API permissions. 2. Authenticate server-side to get an Azure AD access token. 3. Call the Power BI REST API to generate an embed token for the specific report. 4. Pass the embed token and embed URL to the client. 5. Client uses the Power BI JavaScript SDK to render the report in a div. The Azure AD access token stays server-side. Only the embed token (short-lived, report-specific) is sent to the browser.
Q2. In the Power BI JavaScript SDK, what does powerbi.embed(container, config) require in the config object?
- A) The service principal's client secret and tenant ID
- B) The embed URL (from the Power BI REST API), the embed token, and the report type
- C) The report's DAX queries and data model connection string
- D) The user's Azure AD credentials and Power BI workspace URL
Show Answer
B. The embed config requires: type: "report", id (report GUID), embedUrl (from the Power BI REST API response), accessToken (the embed token, NOT an Azure AD token), and optionally settings for filter pane/navigation visibility. The service principal credentials must never reach the client — they stay in the backend.
Q3. An embedded report works in development but shows "You don't have permission to view this report" in production. What are the two most likely causes?
Show Answer
1. The service principal is not a member of the Power BI workspace containing the report. Service principals must be explicitly added to the workspace (as Member or higher) to generate embed tokens for reports in that workspace. 2. The "Allow service principals to use Power BI APIs" toggle is not enabled in the Power BI Admin portal. Without this setting, service principals cannot call Power BI REST APIs regardless of Azure AD permissions.
- Azure AD access token stays server-side — use it to call Power BI REST API and generate an embed token. Only the embed token goes to the browser.
- Service principal must be in the workspace — add it as a Member in Power BI Service. Also enable "Allow service principals to use Power BI APIs" in the Admin portal.
- Embed token includes the report ID and access level — one embed token = one report, one access level (view or edit), one optional effective identity for RLS.
- Power BI JavaScript SDK renders client-side —
powerbi.embed(div, config)with embedUrl and accessToken (embed token) renders the report inside a container div. - Refresh embed tokens before expiry — embed tokens expire (typically 1 hour). Implement a token refresh call in your app to avoid session drops on long-running reports.