A Power BI report embedded in a model-driven app looks like part of the app. The user opens their account record, clicks a tab, sees their sales performance dashboard - no context switch, no separate login. Add row-level security, and each user sees only their data, or their team's, or their region's. The combination is compelling.
The setup is not as simple as the marketing pages suggest. Two things that bite teams most often: the identity flow between Power Apps, Power BI, and the RLS rule, and the environment migration pattern that keeps the embed working when you move the solution from Dev to Prod.
Here is the configuration that has held for us across six deployments.
The architecture
Two paths for authentication:
- User's own Power BI license (pass-through). The user is already signed into Power BI (via their Microsoft account). The iframe passes through the existing session. RLS evaluates against the user's identity.
- Embedded token with effective identity. The app generates a token server-side that impersonates the user. RLS evaluates against the impersonated identity.
Pass-through is simpler to set up; every user needs a Power BI license (Pro at minimum) for it to work. Embedded token is more flexible (license management can be centralized) but requires server-side code and a Power BI Embedded capacity SKU.
For most enterprise deployments, pass-through is fine because users already have Microsoft 365 E3/E5 licenses including Power BI Pro. For B2B or external-user scenarios, embedded token is the only path.
Setting up RLS
In Power BI Desktop:
- In the data model, create a security role (Manage Roles button).
- Write a DAX filter expression: [SalesRep Email] = USERPRINCIPALNAME() for per-rep filtering.
- Publish the report to the workspace.
- In the Power BI service, assign users (or Azure AD groups) to the role.
In the embed scenario, the role is evaluated against:
- USERPRINCIPALNAME() for pass-through auth (returns the signed-in user's UPN).
- The effective identity in the embed token for embedded auth.
The RLS rule is written in DAX against the report's data model. Common patterns:
- Per-user: [AssignedTo] = USERPRINCIPALNAME()
- Per-team: [TeamId] IN (SELECTCOLUMNS(FILTER(Teams, [Member] = USERPRINCIPALNAME()), "tid", [TeamId]))
- Per-region: [Region] = LOOKUPVALUE(UserRegion[Region], UserRegion[Email], USERPRINCIPALNAME())
The per-region pattern requires a UserRegion mapping table in the data model - simpler than it sounds.
The three failure modes we debug most
Failure 1: "No data" for all users after deploy
The embed loads, but every user sees an empty report. The RLS rule is filtering everyone out.
Common cause: the DAX expression references a column or table that doesn't exist in the published model. USERPRINCIPALNAME() works, but the table being filtered has a different column name in Prod than in Dev.
Diagnostic: open the report in the Power BI service, use the "Test as role" feature with a specific user's UPN. If it fails there, the rule is broken in isolation; if it works there but fails in the embed, the identity pass-through is the issue.
Fix: align the schema between environments, or parameterize the DAX rule using Power BI's deployment pipeline parameters.
Failure 2: "Cannot load embedded content" with 401
The iframe loads but shows an auth error.
Common causes:
- The Power BI workspace isn't shared with the user. Fix: add the user or their group to the workspace with the "Viewer" role at minimum.
- The tenant admin has disabled "Embed content in apps" in the Power BI admin settings. Fix: escalate to tenant admin; get the toggle flipped.
- The iframe's src URL has the wrong workspace or report ID (common after a Dev-to-Prod migration). Fix: use Power Platform environment variables to parameterize the URL per environment.
Failure 3: RLS works in Power BI service but not in the embed
Report shows the user's data when they open it directly in Power BI. Same user, same report, embedded in the app - shows everyone's data or no data at all.
Root cause: the embed is authenticating as a different identity than the user's own. This happens when the embed token is generated server-side with a stale or wrong effective identity.
Fix: audit the token generation code. The effective identity should use the user's own UPN from the Power Apps context, not a hardcoded admin user. For pass-through auth (no token generation), check the iframe's URL parameters - some iframe embedding patterns accidentally pass an admin's session.
Environment migration
The part teams get stuck on most. The report is built in Dev pointing at Dev's dataset. The Dev workspace has specific users assigned to roles. You move the model-driven app solution to Prod, and the embed is still pointing at Dev.
The clean pattern:
- Separate workspace per environment: Dev, Test, UAT, Prod. Each has its own copy of the report.
- Datasets pulled from environment-appropriate sources: the Prod workspace's dataset connects to Prod Dataverse; Dev's to Dev.
- Power Platform environment variables hold the workspace ID and report ID per environment. The embed URL is built from these variables.
- Deployment pipeline copies the report from Dev's workspace to higher environments as part of the release process (Power BI's deployment pipelines feature, or REST API scripting).
The Power Platform environment variables step is the key. Without it, the embed URL is hardcoded and breaks on every migration. With it, the same solution zip works in every environment because the embed URL resolves at runtime from the local environment variable value.
The CI pattern
For teams that can invest in automation:
- Report source .pbix files live in git alongside the solution.
- The release pipeline uploads the .pbix to the target Power BI workspace.
- RLS role assignments come from a config file (env-specific), applied via Power BI REST API.
- Environment variable values update with the target workspace/report IDs.
The result: a release that includes Power BI changes deploys them atomically with the model-driven app changes. No one has to remember to "also update the report."
For teams without this automation, the alternative is a manual deployment runbook per release. It works, but mistakes happen; the cost of automating pays back after about five releases.
What we skip
DirectQuery to Dataverse for RLS. DirectQuery mode lets the report query Dataverse live, applying RLS as Dataverse's own security model rather than Power BI's. Conceptually clean. In practice, the query latency is unpredictable, and complex reports build up slow queries. We use DirectQuery only for small, frequently-changing datasets where freshness matters more than latency.
User impersonation for embedded tokens in free tiers. If the client is on Power BI free (Fabric-free workspaces), the embedded token features are not available. We push for at least Pro licensing for anyone who touches the embed.
Complex multi-workspace scenarios. An embed that switches between workspaces based on user role gets brittle fast. We consolidate to one workspace per environment and use role-based report visibility within that workspace.
The validation we do on every deploy
Before considering a Power BI embed deployment done:
- Open the model-driven app as an admin user - embed loads, data visible.
- Impersonate a standard user (via Azure AD "Sign in as this user" or a test account) - embed loads, only their data visible.
- Impersonate a user in a different region - embed loads, only their region visible.
- Migrate the solution to a second environment - embed still works, pointing at the new environment's report.
Four steps, ten minutes. Saves the "it worked in Dev" debugging session.
The embed feature is powerful when it works. The configuration is fiddly enough that it pays to have a known-good pattern rather than rediscovering the traps each project.