See your company like never before
Power BI deep-dives, migration playbooks, and data strategy for enterprise teams.


Join dozens of organizations who have moved to Beyond The Analytics. Book your personalized demo today
App-owns-data or user-owns-data, the F64 cliff that sets your licence bill, how effective identity carries RLS through, and the seams users notice.
Quick answer: There are three routes — secure embed for a no-code iframe, embed for your organization for internal users who sign in with Microsoft Entra ID, and embed for your customers for external users who never touch a Power BI credential. The route you pick sets your licence bill before it settles anything technical.
The architecture question that precedes this one is settled: if a user acts on it, or needs it within seconds, it belongs in the application. Everything else stays in Power BI. What that rule leaves behind is a composition problem nobody documents well. You now have an application and a set of reports, both on the same screen, and the user is supposed to experience one product rather than two.
The pattern that works is unglamorous. An operations command centre renders its live strip — the counts, the alarms, the things that must be current to the second — from the application's own data path. Directly beneath it sits an embedded report carrying the month-over-month analysis. One filter control drives both. The user never learns which half is which, and that is the entire success criterion.
Rebuilding the lower half in your own charting library is the tempting mistake. You would be re-implementing row-level security, scheduled refresh, export to Excel and PowerPoint, subscriptions, lineage, and a self-service surface where an analyst can answer a new question without a sprint. That is years of work to reach parity with a product you are already licensed for. Embed it.
Quick answer: Embed for your customers (app-owns-data) authenticates non-interactively with a service principal or master user, and your end users need no Power BI licence at all. Embed for your organization (user-owns-data) authenticates each user against Microsoft Entra ID, and each of them needs a licence.
Microsoft's embedded analytics overview draws the line as a table, and the two rows that decide your build are these: "App users don't need a license" versus "Each app user needs a Power BI license", and "Non-interactive authentication. Your app uses a service principal or a master user to authenticate" versus "Interactive authentication. Your app uses the app user's credentials to authenticate."
| Embed for your customers | Embed for your organization | |
|---|---|---|
| Also known as | App owns data | User owns data |
| Aimed at | External users | Internal users |
| Authentication | Service principal or master user | The user's own Entra ID credentials |
| End-user licence | None required | Pro or PPU, unless on F64+ or P |
| R and Python visuals | Not supported | Supported, with region restrictions |
The R and Python line is easy to skip and expensive to discover late. If a report in scope renders an R or Python visual, app-owns-data is closed to you.
The choice is usually made for you by who the users are. An internal command centre for staff who already hold Microsoft 365 accounts is user-owns-data. A portal you expose to contractors, regulators, or a joint-venture partner is app-owns-data, because provisioning each of them an Entra identity and a Power BI licence is a procurement project you do not want.
A third route exists and gets over-recommended. Secure embed is a no-code iframe from File → Embed report → Website or portal. It respects RLS and item permissions and takes about a minute to set up. It is also, in Microsoft's own words on that page, degraded: "Due to ongoing Chromium security updates, the Embed option no longer works exactly as it used to, and users may be asked to authenticate more than once." Treat it as a demo tool.
Quick answer: Under user-owns-data, every viewer needs a Pro or Premium Per User licence unless the content sits on an F64-or-larger Fabric capacity or a legacy P SKU. Under app-owns-data there are no end-user licensing requirements at any capacity size. That single difference can be the whole business case.
Microsoft states it twice on the capacity and SKUs page, once in a footnote and once in the limitations: "Only SKUs equivalent to a F64 or higher, allow free Power BI users to consume Power BI apps and shared content in Power BI service. If you have a SKU smaller than F64, then a Pro license or Premium Per User (PPU) is required for each user who will view the embedded content." And for the other model: "For Embedding for your customers (app owns data) customers, there are no licensing requirements for the end users."
Run the arithmetic on a 400-viewer internal deployment and the shape is obvious. Below F64, you are buying 400 Pro seats on top of the capacity. At F64, you are buying the capacity and nothing else. The cliff is not gradual — F32 and F64 are one step apart on the SKU ladder and on opposite sides of a per-seat bill. Sizing the capacity for performance and sizing it for licensing are two different exercises, and on internal embeds the second one usually wins. Fabric capacity sizing from F2 to F64 covers the performance half.
Four SKU families still appear in the documentation, and only three are buyable:
One trap specific to reading these docs from the Gulf. The capacity page carries the note: "Azure Embedded (F SKU) capacities aren't supported in the GCC environment. Only EM and P SKUs are available for use in GCC." GCC there means the US Government Community Cloud, not the Gulf Cooperation Council. It has nothing to do with a UAE or Saudi deployment. The number of architecture documents that have quoted it as though it did is not small.
The real regional constraint is elsewhere. Microsoft's Fabric region availability table, revised 28 July 2026, lists three Middle East regions: Qatar Central, UAE Central and UAE North. Only UAE North carries all Fabric workloads — the other two are marked "Power BI only region". No Saudi Arabia region appears in the table at all. If your data residency position requires in-country Fabric capacity, UAE North is currently the only Gulf answer, and a Saudi requirement resolves to Power BI on a Saudi tenant, an on-premises path, or Multi-Geo. Data residency for Power BI in the Gulf works through the compliance side.
Quick answer: Under app-owns-data, Power BI has no idea who your user is, so your backend asserts it. You generate an embed token carrying an effective identity — a username, one or more RLS roles, and the semantic model IDs — and Power BI filters on what you asserted.
This is where the two surfaces genuinely become one product, and where a mistake is silent rather than loud.
Static roles fix the filter in DAX and ignore the username entirely. Dynamic roles use username() or userprincipalname() in the role definition, so the string you pass is the filter. Microsoft's cloud RLS walkthrough shows the request shape:
{
"accessLevel": "View",
"identities": [
{
"username": "France",
"roles": [ "CountryDynamic" ],
"datasets": [ "fe0a1aeb-f6a4-4b27-a2d3-b5df3bb28bdc" ]
}
]
}Note what "username" is doing there. It is not an email address; it is a filter value. One dynamic role covers every region, and the region name rides in as the effective identity.
Four constraints decide whether this holds up in production:
GenerateToken API. Therefore, if you use the same Microsoft Entra token to generate several embed tokens, the lifetime of the generated embed tokens get shorter with each call." Cache the Entra token aggressively and your last user of the hour gets a session that expires in ninety seconds. Refresh the token before it expires rather than at expiry.At real tenant counts, roles stop scaling and workspace isolation takes over. Microsoft's own recommendation on the token page is unambiguous — "We recommend using workspace-based isolation with profiles" — with service principal profiles giving each customer their own workspace and model. RLS then filters within a tenant rather than between tenants, which is the boundary you actually want to defend.
Quick answer: The Power BI JavaScript client drives the report from your application with updateFilters at report, page or visual level, and reports back through events such as dataSelected and pageChanged. Filters flow both ways, but not every interaction raises an event.
Drive state from one place — your application — and treat the report as a controlled component. updateFilters takes an operation (Add, Replace, ReplaceAll, RemoveAll) and an array of filters, and the three levels are independent: clearing a page filter leaves report-level filters untouched.
await report.updateFilters(models.FiltersOperations.ReplaceAll, [
{
$schema: "https://powerbi.com/product/schema#basic",
target: { table: "Site", column: "SiteCode" },
operator: "In",
values: selectedSites,
filterType: models.FilterType.BasicFilter,
},
]);Two details cost people days.
The first is performance, and it is counter-intuitive. Microsoft's embedded performance guidance states: "Usually, report visuals are saved with cached data. Reports render the cached data while queries are executed. If filters, bookmarks, or slicers are provided, cached data isn't used and the visuals are rendered only after the visual query has ended." Passing a filter at embed time buys you correctness and costs you the fast first paint. Where the default view is predictable, save the report with those filters already applied and let the cache do its job.
The second is the return path. dataSelected fires when a user selects a data point, and it carries the report, page, visual, filters and data points — but the documentation notes plainly that "Range and relative slicers are not supported." A user dragging a date range inside the report will not tell your application anything. If date range has to drive both surfaces, own that control in your application and push it into the report. Do not try to read it back out.
For anything that has to be current to the second, the report is the wrong surface entirely — refresh floors and the latency budget explain why, and the answer is the same as it was in the hub: that panel is application code.
Quick answer: Users do not notice architecture. They notice a second sign-in prompt, a theme that does not match, a spinner where the rest of the page has already painted, and an export that produces a file with someone else's branding on it.
Load time. An unprepared iframe is the most visible seam. The client SDK gives you two levers: powerbi.preload() downloads the JavaScript, CSS and supporting items ahead of time when the report is not on the landing page, and powerbi.bootstrap(element, config) — SDK version 2.9 or later — prepares and initialises the iframe before all embed parameters are available, so the frame construction and your backend's token call run in parallel. Also request View permissions rather than Edit when you have no intention of offering editing: "This way, time isn't spent initializing components that are only used in edit mode." And when the user switches reports, re-embed into the same iframe rather than creating a new one.
Theming. A report in Power BI's default palette inside your application reads as bolted on, immediately. Pass a theme in the embed configuration's theme property, or call report.applyTheme({ themeJson }) after load. One rule matters: applyTheme replaces the report's theme rather than extending it, so the JSON has to be complete — a partial theme silently drops back to defaults for everything it omits. Generate it from the same design tokens your application uses instead of hand-maintaining a second palette. For Arabic deployments the RTL and bilingual patterns apply to the embedded report exactly as they do to a standalone one.
Authentication. If you took the secure embed route, the limitations list is the seam list: the user signs in again for every new browser window or tab, pop-ups must be enabled, the authentication token defaults to a one-hour lifetime that "You can't automatically refresh", Azure B2B guest accounts are not supported at all, and a Cross-Origin-Opener-Policy header set to same-origin breaks sign-in outright because MSAL does not support it. That last one is a genuinely nasty interaction — a sensible security header on your own application disables the embed. Microsoft's suggested values are restrict-properties or same-origin-allow-popups. Secure embed is also blocked from the JavaScript API in client SDK versions from 2.10.4 onward, so any ambition to sync filters rules it out.
Filters are not permissions. Worth stating because it gets designed in backwards. Microsoft on URL filters: "URL filters are not a security feature and should not be used to restrict access to sensitive data. Users can modify or remove URL parameters to view unfiltered data." The same holds for filters set through the JavaScript API. Anything a user must not see is RLS or object-level security in the model, or it is not enforced.
Quick answer: The capacity is the visible cost and rarely the surprising one. The surprises are per-seat licences under user-owns-data below F64, the Pro licence every publisher still needs, and engineering time on identity plumbing that no capacity SKU covers.
Three lines make up the bill.
Capacity. One F or A SKU, sized for concurrency and for the F64 licensing threshold if you are on user-owns-data. Azure-purchased capacity can be paused and resumed, which is real money on an internal system nobody opens between 8pm and 6am, and reserved pricing is the trade-off against that flexibility. Verify current rates for your region on the Azure pricing calculator rather than trusting any figure in a blog post, this one included.
Licences. Under app-owns-data, zero for viewers. Under user-owns-data below F64, one Pro or PPU seat per viewer. In both models, anyone publishing content still needs Pro or PPU — Microsoft's exception is narrow and specific: "You can publish content without a Pro or PPU license by using a service principal executing the REST API, Post Import In Group." Pro versus Premium Per User versus Fabric prices the per-seat side properly.
Engineering. The line item most estimates omit. A token service with correct caching, effective identity assertion and refresh-before-expiry; a theme pipeline; filter state synchronisation; and error handling for the failure modes above. On a first embed, budget two to four weeks of a senior engineer's time before the first report renders correctly for the second tenant. It is not exotic work, but it is not free, and it is not what the capacity SKU is paying for.
The comparison worth running is not embedded versus a cheaper BI tool. It is embedded versus rebuilding self-service analytics, RLS, exports and scheduled refresh inside your application. Priced honestly, that second option is never cheaper — which is exactly why the composition problem is worth solving properly instead of avoiding.
If you are still choosing where the reporting layer lives at all, Fabric Apps versus a custom web app prices both sides of the hosting decision, and Beyond The Analytics builds these composed surfaces for UAE and Saudi enterprises.
App-owns-data (embed for your customers) authenticates non-interactively using a service principal or master user, and end users need no Power BI licence — typically used by ISVs serving external customers. User-owns-data (embed for your organization) authenticates each user against Microsoft Entra ID, and each user needs a Power BI Pro or Premium Per User licence unless the content sits on an F64-or-higher Fabric capacity or a legacy P SKU. App-owns-data does not support R or Python visuals.
It depends on the embed model. Under app-owns-data, Microsoft states there are no licensing requirements for end users at any capacity size. Under user-owns-data, every viewer needs a Pro or PPU licence unless the workspace is on an F64-or-larger Fabric capacity or a P SKU, where free users can consume the content. Anyone publishing content still needs Pro or PPU regardless of model, unless publishing through a service principal calling the Post Import In Group REST API.
Yes. Under app-owns-data you pass an effective identity in the embed token containing a username, the RLS roles to apply, and the relevant semantic model IDs. Static roles ignore the username; dynamic roles built on username() or userprincipalname() use the string you pass as the filter value. Use a service principal rather than a master user — if the identity is omitted, service principal token generation fails, whereas a master user succeeds and silently returns the entire model.
The embed token inherits the remaining lifetime of the Microsoft Entra token used to call the GenerateToken API. Reusing the same Entra token across several embed token requests produces progressively shorter-lived embed tokens. Applications should refresh the embed token before expiry rather than at it. Secure embed behaves differently: its authentication token defaults to a one-hour lifetime set by your Microsoft Entra configuration and cannot be refreshed automatically.
Microsoft's Fabric region availability list, revised 28 July 2026, names three Middle East regions. UAE North supports all Fabric workloads. UAE Central and Qatar Central are marked "Power BI only region", meaning no F SKU capacity. No Saudi Arabia region appears in the table. For an in-region Fabric capacity in the Gulf today, UAE North is the answer. Note that the "GCC environment" referenced in Microsoft's embedded capacity documentation means the US Government Community Cloud, not the Gulf Cooperation Council.
Use secure embed for internal demos and low-stakes portals where every viewer already holds a licence. Use the JavaScript API for anything user-facing. Secure embed cannot refresh its authentication token automatically, requires sign-in on each new browser window, does not support Azure B2B guest accounts, breaks when the host page sets a Cross-Origin-Opener-Policy of same-origin, and is blocked from the JavaScript API in client SDK versions 2.10.4 and later — which rules out filter synchronisation between your application and the report.
Pass a complete theme JSON in the embed configuration's theme property, or call report.applyTheme after the report loads. The applyTheme API replaces the report's theme rather than extending it, so a partial theme falls back to Power BI defaults for anything it omits. Generate the theme from the same design tokens your application uses so the two stay in step. Combine it with powerbi.preload() and powerbi.bootstrap() so the report paints alongside the rest of the page rather than after it.
Microsoft Partner · Dubai
Your business intelligence partner for the GCC
Have a data challenge or a project in mind? Reach out and let's explore how we can help.
Clients we've worked with






