Power BI Advanced Series Β· Power BI Embedding & Security Β· by Raushan Ranjan, MCT
This is your comprehensive hands-on guide for demonstrating a powerful Power BI embedding scenario: integrating reports with Row-Level Security (RLS) into an ASP.NET Core MVC application using the "App-Owns-Data" model. This approach is ideal for custom web applications where you want to control data access at a granular level for your users, regardless of their Power BI licenses.
β What Youβll Learn
- How to design RLS in Power BI Desktop for the App-Owns-Data model.
- How to register and set up an application in Azure AD for Power BI embedding.
- How to generate secure embed tokens with RLS identities using your ASP.NET Core backend.
- How to seamlessly embed the Power BI report into your MVC application.
- How to simulate different users to effectively test RLS in an embedded context.
π§° Prerequisites
| Tool | Required Version |
|---|---|
| Power BI Desktop | Latest |
| Visual Studio | 2022+ |
| ASP.NET Core SDK | 8.0+ |
| Azure AD Access | Admin rights preferred |
| Power BI Pro / PPU | Required for publishing reports and managing workspaces. You will also need a Power BI Premium (P SKU) or Power BI Embedded (A SKU) capacity for App-Owns-Data embedding. |
π§ Designing for RLS with the App-Owns-Data Model
π§ What is RLS (Row-Level Security)?
RLS (Row-Level Security) is a feature in Power BI that filters the data a user can see based on their identity or role. Instead of giving everyone access to the full dataset, you can show only the relevant rows of data to each user, ensuring data confidentiality and compliance.
π§ What is App-Owns-Data Model?
In this model:
- Your application controls the authentication (not the Power BI service directly).
- End-users of your application typically donβt need a Power BI account or license.
- Your application embeds the report and generates a secure embed token on behalf of the user.
- You can precisely control what data each user sees by applying RLS through the embed token.
β Designing for RLS in this model
To design RLS for the App-Owns-Data model, you define roles and filters within Power BI Desktop, and then your application's backend will tell Power BI which role to apply.
| Step | Description |
|---|---|
| 1οΈβ£ Define Roles | Inside Power BI Desktop, use Modeling > Manage Roles to create one or more roles (e.g., "EastRegionRole", "SalesRole"). |
| 2οΈβ£ Create DAX Filter | For each role, create a DAX filter expression that determines which rows are visible. This filter will typically reference a column in your data model. For dynamic filtering based on the embed token's `username`, use USERNAME() or USERPRINCIPALNAME(). |
| 3οΈβ£ Publish Report | Publish the report to a Power BI Workspace that is backed by a Power BI Premium or Embedded capacity. |
| 4οΈβ£ Pass Identity & Role | In your backend application, when generating the embed token, you'll pass the user's identity (via `username`) and the RLS role(s) to apply using the `EffectiveIdentity` object. |
| 5οΈβ£ Power BI Applies Filter | Power BI receives the embed token, applies the specified role-based filter to the dataset, and then renders the report to that user with only the authorized data. |
π©βπ» Example:
In Power BI Desktop:
- Role:
EastRegionRole - DAX Filter:
[Region] = "East"
When embedding, your C# backend code would specify this role:
var identity = new EffectiveIdentity("raushan@demo.com", new List<string> { datasetId })
{
Roles = new List<string> { "EastRegionRole" }
};
This ensures only "East Region" data is shown for the user associated with "raushan@demo.com".
π 2. Generating Embed Tokens Restricted by RLS Roles
π§ Why Generate Embed Tokens?
Embed Tokens are short-lived, secure keys used by your application to securely display Power BI reports without exposing any Power BI credentials. Each token contains specific information about:
- What report to load.
- What dataset to use.
- What access level is allowed (e.g., View, Edit, Create).
- Crucially, what RLS role(s) to apply (if any).
β Steps to Generate RLS-Sensitive Embed Tokens
| Step | Description |
|---|---|
| 1οΈβ£ Authenticate Backend | Use your Azure AD App (Service Principal) to authenticate your backend application with Azure AD. This grants your app access to Power BI APIs. |
| 2οΈβ£ Call Power BI REST API | Use the Power BI .NET SDK (or directly call the REST API) to interact with the Power BI Service. |
3οΈβ£ Create EffectiveIdentity |
Create an EffectiveIdentity object. This object tells Power BI "who" the user is (via `username`) and "which RLS role(s)" should apply to them. You also specify the `datasetId` this identity applies to. |
| 4οΈβ£ Build Token Request | Pass this `EffectiveIdentity` object within a `GenerateTokenRequestV2` (or similar) object. You also specify the report ID, dataset ID, and desired access level. |
| 5οΈβ£ Get Token | Call the Power BI API's `GenerateTokenAsync` (or `GenerateTokenInGroupAsync`) method to retrieve the embed token. |
π¨βπ» Sample C# Code:
This snippet demonstrates how to construct the `EffectiveIdentity` and the token generation request in C# using the Power BI .NET SDK.
// Assume 'datasetId', 'reportId', 'workspaceId' are Guids obtained from Power BI Service
// Assume 'pbiClient' is an authenticated PowerBIClient instance
var effectiveIdentity = new EffectiveIdentity("raushan@demo.com", new List<string> { datasetId.ToString() })
{
Roles = new List<string> { "EastRegionRole" } // RLS role defined in Power BI Desktop
};
var tokenRequest = new GenerateTokenRequestV2
{
Reports = new List<GenerateTokenRequestV2Report>
{
new GenerateTokenRequestV2Report { Id = Guid.Parse(reportId) }
},
Datasets = new List<GenerateTokenRequestV2Dataset>
{
new GenerateTokenRequestV2Dataset { Id = Guid.Parse(datasetId) }
},
Identities = new List<EffectiveIdentity> { effectiveIdentity },
AccessLevel = "View"
};
var embedToken = await pbiClient.EmbedToken.GenerateTokenInGroupAsync(
Guid.Parse(workspaceId),
tokenRequest
);
β Important Notes
- The `username` provided in the `EffectiveIdentity` **does not need to be a real Power BI user account**. It's a string identifier that Power BI's `USERNAME()` or `USERPRINCIPALNAME()` DAX functions will return. This allows you to integrate with your own application's user management system.
- Make sure the `role` names in the `EffectiveIdentity` (e.g.,
"SalesRole"or"EastRegionRole") **exactly match** those defined in your Power BI Desktop file (case-sensitive). - Embed tokens are typically valid for **1 hour**. Your application should handle token refreshing before expiration to provide a continuous experience.
π§© STEP 1: Setup RLS in Power BI Desktop
First, we'll define the RLS role directly in your Power BI Desktop file. This role will filter data based on a `UserName` column, which we'll later pass via the embed token.
π§ Steps:
- Open your
.pbixfile in Power BI Desktop. Ensure your dataset has a column that can be used for filtering (e.g., a `Salesperson` or `Region` column) and a corresponding column in a mapping table that will match the `username` we pass. For simplicity, we'll assume a `UserName` column in your data table that matches the email you'll simulate. - Click on the Model View (the three-tables icon on the left panel).
- In the top ribbon, click Modeling β Manage Roles.
- In the "Manage roles" window, click Create. Name the role:
SalesRole. - Select the table you want to apply the filter to (e.g., your `Sales` table).
- In the DAX Filter expression box, add the following DAX:
This DAX expression will filter the data in the selected table where the `UserName` column matches the `username` provided in the embed token.[UserName] = USERNAME() - Click Save.
- Save your
.pbixfile. - Publish it to a workspace in Power BI Service. Ensure this workspace is backed by a Premium or Embedded capacity.
π§© STEP 2: Register Application in Azure AD
Your ASP.NET Core MVC application needs to be registered in Azure Active Directory (Azure AD) to get the necessary credentials and permissions to interact with Power BI APIs.
π Azure Portal β Azure Active Directory β App registrations β New registration
| Field | Value |
|---|---|
| Name | PowerBI-MVC-App (or a name of your choice) |
| Supported account types | Choose the appropriate option for your organization (e.g., "Accounts in this organizational directory only"). |
| Redirect URI (Optional) | Select "Web" and add https://localhost:7031/signin-oidc. This is the default port for new ASP.NET Core 8.0 projects in Visual Studio. Adjust if your project uses a different port. |
After Registration:
- From the "Overview" blade of your newly registered app, save the following:
- Application (client) ID
- Directory (tenant) ID
- Go to Certificates & secrets.
- Click New client secret.
- Provide a description and choose an expiration.
- Immediately save the "Value" of the secret. This value is only shown once!
- Go to API permissions.
- Click Add a permission.
- Select Microsoft Graph and add the Delegated permission:
User.Read. - Click Add a permission again. Select Power BI Service (under "APIs my organization uses" or "Microsoft APIs").
- Add the following Application permissions (as your app will act on its own behalf):
Dataset.Read.AllReport.Read.AllWorkspace.Read.AllEmbed.Read.All
- Crucial: Click "Grant admin consent for [Your Tenant]". This step is often overlooked and causes permission errors.
π§© STEP 3: Assign Service Principal to Power BI Workspace
While `USERNAME()` in DAX works with the `username` passed in the embed token, for the "App Owns Data" model, your Azure AD App (Service Principal) needs access to the Power BI workspace.
- Go to Power BI Service (app.powerbi.com).
- Navigate to your Workspace where the report is published.
- Click Access (or the three dots
...next to the workspace name, then "Workspace access"). - Add your Azure AD App (Service Principal) as a member with at least Contributor role (or Admin for full control). Search for the "Name" of your Azure AD App Registration (e.g., "PowerBI-MVC-App").
- Note: You do NOT assign individual users to RLS roles in Power BI Service for App-Owns-Data. The RLS is enforced by the `identities` array in the embed token.
π§© STEP 4: Prepare MVC Backend Code
Now, let's set up the ASP.NET Core backend to generate the embed tokens.
π§ TokenService.cs (Create this file in a `Services` folder in your MVC project)
using Azure.Identity;
using Microsoft.PowerBI.Api;
using Microsoft.PowerBI.Api.Models;
using Microsoft.Rest;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace PowerBIEmbeddingDemo.Services
{
public class TokenService
{
// REPLACE these with your actual IDs and Secret from Azure AD and Power BI Service
private const string tenantId = "YOUR_TENANT_ID";
private const string clientId = "YOUR_CLIENT_ID";
private const string clientSecret = "YOUR_CLIENT_SECRET";
private const string reportId = "YOUR_REPORT_ID"; // Found in Power BI report URL
private const string datasetId = "YOUR_DATASET_ID"; // Found in Power BI dataset URL
private const string workspaceId = "YOUR_WORKSPACE_ID"; // Found in Power BI workspace URL (groupId)
public async Task<string> GetEmbedTokenAsync(string username)
{
// Authenticate with Azure AD using Client Credentials flow (Service Principal)
var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
var token = await credential.GetTokenAsync(
new Azure.Core.TokenRequestContext(new[] { "https://analysis.windows.net/powerbi/api/.default" }));
// Create a Power BI client using the acquired access token
var tokenCredentials = new TokenCredentials(token.Token);
using var powerBIClient = new PowerBIClient(new Uri("https://api.powerbi.com/"), tokenCredentials);
// Define the effective identity for RLS
var effectiveIdentity = new EffectiveIdentity(
username, // This 'username' will be passed to USERNAME() DAX function in Power BI
new List<string> { datasetId }, // Specify the dataset(s) for RLS
new List<string> { "SalesRole" }); // Specify the RLS role(s) to apply
// Create the request to generate the embed token
var generateTokenRequestParameters = new GenerateTokenRequestV2
{
AccessLevel = "View", // Access level for the token (View, Edit, Create)
Identities = new List<EffectiveIdentity> { effectiveIdentity }
};
// Generate the embed token for the report
var tokenResponse = await powerBIClient.Reports.GenerateTokenAsync(workspaceId, reportId, generateTokenRequestParameters);
return tokenResponse.Token;
}
}
}
Important: Remember to install the necessary NuGet packages: Azure.Identity and Microsoft.PowerBI.Api.
π§© STEP 5: Embed Report Page in MVC
Next, we'll create an MVC Controller action and its corresponding View to display the embedded report.
π§ Controller: EmbedController.cs (Create this in your `Controllers` folder)
using Microsoft.AspNetCore.Mvc;
using PowerBIEmbeddingDemo.Services;
using System.Threading.Tasks;
namespace PowerBIEmbeddingDemo.Controllers
{
public class EmbedController : Controller
{
private readonly TokenService _tokenService;
public EmbedController(TokenService tokenService)
{
_tokenService = tokenService;
}
public async Task<IActionResult> Report()
{
// Simulate a user. In a real app, this would come from your authentication system (e.g., HttpContext.User.Identity.Name).
string testUser = "neha@yourdomain.com"; // REPLACE with an email that exists in your RLS mapping data in Power BI
var token = await _tokenService.GetEmbedTokenAsync(testUser);
// Pass data to the View using ViewBag
ViewBag.EmbedToken = token;
ViewBag.ReportId = "YOUR_REPORT_ID"; // Same as in TokenService.cs
ViewBag.EmbedUrl = "https://app.powerbi.com/reportEmbed?reportId=YOUR_REPORT_ID&groupId=YOUR_WORKSPACE_ID"; // REPLACE with your report embed URL (from Power BI Service)
return View();
}
}
}
π§ View: Report.cshtml (Create this file in `Views/Embed` folder)
@{
ViewData["Title"] = "Embedded Report";
}
<h2>@ViewData["Title"]</h2>
<div class="text-center">
<div id="reportContainer" style="height:600px; width:90%; margin:auto; border:1px solid #ccc;"></div>
</div>
@section Scripts {
<script src="https://cdn.jsdelivr.net/npm/powerbi-client@2.19.1/dist/powerbi.min.js"></script>
<script>
var embedConfig = {
type: 'report',
id: '@ViewBag.ReportId',
embedUrl: '@ViewBag.EmbedUrl',
accessToken: '@ViewBag.EmbedToken',
tokenType: powerbi.models.TokenType.Embed, // Use models.TokenType.Embed for App-Owns-Data
settings: {
panes: {
filters: { visible: false }, // Hide filter pane by default
pageNavigation: { visible: true } // Show page navigation
}
}
};
var reportContainer = document.getElementById('reportContainer');
powerbi.embed(reportContainer, embedConfig);
</script>
}
π§© STEP 6: Register Service in Program.cs
You need to register your `TokenService` with ASP.NET Core's dependency injection container. Add the following line in your `Program.cs` file, typically after `builder.Services.AddControllersWithViews();`:
builder.Services.AddScoped<TokenService>();
π§ͺ STEP 7: Run and Test the Demo
- Run your ASP.NET Core MVC application from Visual Studio. It should open in your browser, typically at
https://localhost:7031. - Navigate to the report page by going to
https://localhost:7031/Embed/Report(or whatever route you configured for your `Report` action). - The Power BI report should load within the defined container, and crucially, it should display only the data visible to the user simulated in your `EmbedController.cs` (e.g.,
neha@yourdomain.com). - To simulate another user with different RLS access, simply change the
testUservariable inEmbedController.csand re-run the application.
π§Ύ Bonus: Sample Test Users
To effectively test RLS, ensure you have multiple entries in your Power BI dataset's mapping table (the one linked to your `UserName` column) with different users and their corresponding data segments.
For example, if you have a user raushan@yourdomain.com, make sure this user is associated with a different data segment (e.g., "West Region") in your Power BI data model, and then update:
string testUser = "raushan@yourdomain.com";
This will generate an embed token for "raushan@yourdomain.com", and the report should then display data filtered for the "West Region" (assuming your RLS role is set up correctly).
π― Summary (TL;DR)
| Concept | Meaning |
|---|---|
| RLS | Filters data per user using roles defined in Power BI Desktop. |
| App-Owns-Data | Your application authenticates with Power BI, and end-users donβt need Power BI accounts. |
| EffectiveIdentity | A crucial object in the embed token request that tells Power BI "who" the user is (`username`) and "which role(s)" should apply to them. |
| Embed Token | A short-term, secure token generated by your backend that controls what Power BI content the user sees and which RLS filters are applied. |
Quick Knowledge Check
Q1. In an ASP.NET MVC controller, which class from the Power BI .NET SDK do you use to generate an embed token, and what parameter carries the RLS effective identity?
Show Answer
The PowerBIClient class with its Reports.GenerateTokenInGroupAsync() method. The request body is a GenerateTokenRequest object. To apply RLS, populate its Identities list with an EffectiveIdentity object containing the username (e.g., the currently logged-in MVC user's email) and the dataset ID. Without an entry in Identities, RLS is bypassed when using a service principal.
Q2. In the ASP.NET MVC app-owns-data pattern, where should the ClientSecret and TenantId for the Azure AD service principal be stored?
- A) Hard-coded in the controller action for easy access
- B) In a configuration file (
appsettings.json) or, for production, Azure Key Vault / environment variables
- C) In a JavaScript file served to the client browser
- D) In the Power BI report's dataset connection string
Show Answer
B. Credentials should be read from appsettings.json during development (and excluded from source control via .gitignore). In production, store them in Azure Key Vault, environment variables, or a managed identity. Hard-coding in source code risks exposure via version control history. Serving to the client in JavaScript exposes credentials to every browser user.
Q3. What HTTP response does your ASP.NET MVC action return to the Razor view, and how does the JavaScript embed code use it?
Show Answer
The action returns a JSON response (or populates a ViewModel) containing: embedToken, embedUrl, and reportId. The Razor view renders a div container and includes the Power BI JavaScript SDK. On page load, client-side JavaScript reads the token and URL from the JSON/ViewModel, then calls powerbi.embed(container, { type: "report", id: reportId, embedUrl, accessToken: embedToken }) to render the report. The embed token is the ONLY credential that reaches the browser.
5 Things to Remember
- PowerBIClient.Reports.GenerateTokenInGroupAsync() β the .NET SDK method for generating embed tokens. Pass EffectiveIdentity in the Identities list for RLS.
- Store secrets in config or Key Vault β never hard-code ClientSecret in source. Use appsettings.json locally; Azure Key Vault or environment variables in production.
- Controller returns embedToken, embedUrl, reportId to the view β either as JSON for AJAX or as a ViewModel property for server-side rendering. Only the embed token reaches the browser.
- Client calls powerbi.embed() with the token β the JavaScript SDK handles report iframe rendering. Token expiry requires a server round-trip to generate a fresh token.
- Service principal must be in the workspace + Admin setting enabled β two-step prerequisite: workspace membership AND tenant admin API toggle. Missing either causes 403 errors.
Quick Knowledge Check
Q1. In an ASP.NET MVC controller, which class from the Power BI .NET SDK do you use to generate an embed token, and what parameter carries the RLS effective identity?
Show Answer
The PowerBIClient class with its Reports.GenerateTokenInGroupAsync() method. The request body is a GenerateTokenRequest object. To apply RLS, populate its Identities list with an EffectiveIdentity object containing the username (e.g., the currently logged-in MVC user's email) and the dataset ID. Without an entry in Identities, RLS is bypassed when using a service principal.
Q2. In the ASP.NET MVC app-owns-data pattern, where should the ClientSecret and TenantId for the Azure AD service principal be stored?
- A) Hard-coded in the controller action for easy access
- B) In a configuration file (
appsettings.json) or, for production, Azure Key Vault / environment variables - C) In a JavaScript file served to the client browser
- D) In the Power BI report's dataset connection string
Show Answer
B. Credentials should be read from appsettings.json during development (and excluded from source control via .gitignore). In production, store them in Azure Key Vault, environment variables, or a managed identity. Hard-coding in source code risks exposure via version control history. Serving to the client in JavaScript exposes credentials to every browser user.
Q3. What HTTP response does your ASP.NET MVC action return to the Razor view, and how does the JavaScript embed code use it?
Show Answer
The action returns a JSON response (or populates a ViewModel) containing: embedToken, embedUrl, and reportId. The Razor view renders a div container and includes the Power BI JavaScript SDK. On page load, client-side JavaScript reads the token and URL from the JSON/ViewModel, then calls powerbi.embed(container, { type: "report", id: reportId, embedUrl, accessToken: embedToken }) to render the report. The embed token is the ONLY credential that reaches the browser.
- PowerBIClient.Reports.GenerateTokenInGroupAsync() β the .NET SDK method for generating embed tokens. Pass EffectiveIdentity in the Identities list for RLS.
- Store secrets in config or Key Vault β never hard-code ClientSecret in source. Use appsettings.json locally; Azure Key Vault or environment variables in production.
- Controller returns embedToken, embedUrl, reportId to the view β either as JSON for AJAX or as a ViewModel property for server-side rendering. Only the embed token reaches the browser.
- Client calls powerbi.embed() with the token β the JavaScript SDK handles report iframe rendering. Token expiry requires a server round-trip to generate a fresh token.
- Service principal must be in the workspace + Admin setting enabled β two-step prerequisite: workspace membership AND tenant admin API toggle. Missing either causes 403 errors.