An interactive visual guide for BI professionals — how embedding works, row-level security, embed tokens, and multi-tenant isolation.
Take your Power BI reports and display them inside your own web application. End users see full interactivity — slicers, filters, drill-through — without ever visiting app.powerbi.com.
Build in Power BI Desktop, publish to a workspace — exactly the same workflow you know. Nothing changes in how you author.
End users do not need Power BI Pro or PPU licenses. Your app's capacity handles everything.
Reports render inside your app's UI. You control the navigation, layout, and branding around the report.
Choose based on who your audience is and whether they have Power BI licences.
Your application authenticates with a service principal. End users have no Entra ID / Power BI interaction.
| Who logs in? | Your app (service principal) |
| User licence? | Not needed |
| Best for | External users, ISV apps, portals |
Each user logs in to Entra ID themselves. They need their own Pro or PPU licence.
| Who logs in? | Each user via Entra ID |
| User licence? | Pro or PPU required |
| Best for | Internal dashboards, intranet apps |
Three layers — Power BI, Azure, and your web application.
Follow the journey from report publishing to a user seeing the embedded report.
Open your report in the Power BI Service. The URL is:
https://app.powerbi.com/groups/WORKSPACE-ID/reports/REPORT-ID
Tenant ID and Client ID come from the Entra ID app registration (set up by IT/admin).
Your app talks to Power BI through a set of REST APIs. Here are the key endpoints involved in embedding, what each one does, and how they chain together.
Before calling any Power BI API, your server must get an Entra ID access token by authenticating with your Tenant ID, Client ID, and Client Secret via the Microsoft Identity platform (https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token). This access token is sent as a Bearer header on every subsequent API call.
Lists all workspaces the service principal has access to. Useful for multi-tenant setups where each client has their own workspace.
Returns dataset metadata including whether RLS is configured. Helps your app decide whether to include identities in the token request.
Triggers a data refresh. You can automate this to keep embedded reports up to date without logging in to the Power BI Service.
Lists all pages in a report. Useful if your app needs to show a page picker or navigate programmatically to a specific page.
All Power BI REST API calls go to: https://api.powerbi.com/v1.0/myorg/
The v1.0 version is stable and used in production. Microsoft publishes updates via the REST API reference docs.
Control which rows each user sees — same DAX roles as always, but your app assigns the identity instead of Entra ID.
| Region | Provider | Revenue |
|---|---|---|
| East | Alpha Care | £42,000 |
| East | Beta Health | £38,500 |
| West | Gamma Med | £51,200 |
| West | Delta Labs | £29,800 |
| North | Epsilon Care | £33,100 |
The DAX filter is hardcoded in the role definition. The username passed in the token is ignored.
[Region] = "East"
Use when: you have a small, fixed number of data segments (regions, departments) and want a named role for each.
| UserUPN | ProviderID | Provider | Revenue |
|---|---|---|---|
| alice@contoso.co.uk | P001 | Alpha Care | £42,000 |
| bob@contoso.co.uk | P002 | Beta Health | £38,500 |
| carol@contoso.co.uk | P003 | Gamma Med | £51,200 |
One role, many users. The username in the token replaces USERPRINCIPALNAME() at query time.
[UserUPN] = USERPRINCIPALNAME()
Recommended. Define once — the identity value does the filtering. No new roles needed when you add users.
| UserUPN | ProviderID | Provider | Revenue |
|---|---|---|---|
| alice@contoso.co.uk | P001 | Alpha Care | £42,000 |
| bob@contoso.co.uk | P002 | Beta Health | £38,500 |
| carol@contoso.co.uk | P003 | Gamma Med | £51,200 |
| dan@contoso.co.uk | P004 | Delta Labs | £29,800 |
Once any RLS role exists on a dataset, every token request must include an EffectiveIdentity. There's no "skip RLS" option.
Solution: Create an AllAccess role with an empty DAX filter (no restriction). Pass it for admin users.
Role: AllAccess
DAX filter: (none)
→ All rows visible
| Problem | Cause | Fix |
|---|---|---|
| 400 "requires effective identity" | Dataset has RLS roles but no identity was passed | Always send EffectiveIdentity when RLS is defined |
| 400 "shouldn't have effective identity" | Dataset has no RLS roles but you passed an identity | Remove the identities field from the token request |
| User sees no data | username doesn't match any rows |
Check exact value — case-sensitive, must match data |
| User sees all data | Role has no filter / broken relationships | Verify relationship chain from security table to facts |
A temporary, scoped key that grants a browser access to a specific Power BI report. The browser cannot escalate or modify it.
When your server calls POST /GenerateToken, Power BI returns a JWT that encodes:
POST /GenerateToken with the report, dataset, and optional RLS identityreport.setAccessToken() for seamless refreshWhen multiple clients use the same embedded app, you need to ensure data separation, performance isolation, and security.
| Shared + RLS | Separate Workspaces | Separate Capacities | |
|---|---|---|---|
| Data isolation | Logical (DAX) | Physical | Physical |
| Performance | ❌ Shared | ⚠️ Partial | ✅ Full |
| Cost | ✅ Lowest | ⚠️ Medium | ❌ Highest |
| Customisation | Limited | Full | Full |
| Risk if misconfigured | ❌ Data leak | ⚠️ Low | ✅ Lowest |
| Management | ✅ Low | ⚠️ Medium | ❌ High |
To embed reports for external users (App Owns Data), you need dedicated capacity.
Pause/resume on demand. Scale up/down via Azure Portal. Billed per hour.
Full Fabric features (Lakehouse, Notebooks, etc.) plus embedding. Fixed monthly cost.
For development and testing, you can use a Pro licence with limited API calls — you don't need a paid capacity until you go to production.
| Concept | What It Means for You |
|---|---|
| Publish reports | Same as always — Desktop → Publish → Workspace |
| App registration | One-time Entra ID setup to give your app API access |
| Embed token | Short-lived JWT generated server-side, scoped to a single report |
| JS SDK | Renders the report in the browser — you don't build a viewer |
| RLS | Same DAX roles as always, but the app passes the identity |
| No user licences | End users don't need Pro/PPU — the capacity handles it |
| Workload isolation | Choose from shared RLS → separate workspaces → separate capacities |