Home / Power BI / Embedded Analytics: Power BI Embedded Implementation
Power BI

Embedded Analytics: Power BI Embedded Implementation

Master Power BI Embedded implementation: comprehensive capacity planning, service principal authentication, JavaScript API integration patterns, multi-tenant...

What you will learn

Practical execution with concise explanations, real implementation patterns, and production-ready recommendations.

Embedded Analytics: Power BI Embedded Implementation

[int]$ConcurrentUsers = 100, [int]$ReportsPerUser = 2, [int]$VisualsPerReport = 10, [decimal]$AvgQueryTimeMs = 500 )

Rough estimation formula

$totalVisuals = $ConcurrentUsers * $ReportsPerUser * $VisualsPerReport $queriesPerSecond = $totalVisuals * (1000 / $AvgQueryTimeMs)

Capacity benchmarks (queries/sec sustained)

$capacityBenchmarks = @{ "A1" = 5 "A2" = 10 "A3" = 20 "A4" = 40 "A5" = 80 "A6" = 160 }

Write-Host "`n=== Embedded Capacity Recommendation ===" -ForegroundColor Cyan Write-Host "Concurrent Users: $ConcurrentUsers" Write-Host "Reports per User: $ReportsPerUser" Write-Host "Visuals per Report: $VisualsPerReport" Write-Host "Avg Query Time: $($AvgQueryTimeMs)ms" Write-Host "Estimated Queries/Sec: $([math]::Round($queriesPerSecond, 2))"

Write-Host "nRecommended SKU:" -ForegroundColor Yellow foreach ($sku in $capacityBenchmarks.GetEnumerator() | Sort-Object Value) { if ($queriesPerSecond -le $sku.Value) { Write-Host " $($sku.Key) - Supports up to $($sku.Value) queries/sec" -ForegroundColor Green Write-Host " Estimated Cost: ~$$($sku.Key.Substring(1))/hour (with auto-pause: ~`$$([math]::Round([int]$sku.Key.Substring(1) * 0.3, 2))/hour average)" return } }

Write-Host " ⚠️ Workload exceeds A6 capacity - consider multiple capacities or optimization" -ForegroundColor Red``` }

Example usage

Get-EmbeddedCapacityRecommendation -ConcurrentUsers 50 -ReportsPerUser 1 -VisualsPerReport 8


## Create Embedded Capacity

```powershell




## Azure CLI: Create Power BI Embedded capacity

$resourceGroup = "rg-powerbi-embedded"
$location = "East US"
$capacityName = "pbiembed-prod-001"
$sku = "A3"  # 4 vCores, 10GB RAM





## Create resource group
az group create --name $resourceGroup --location $location





## Create Power BI Embedded capacity
az powerbi embedded-capacity create `
```text
--resource-group $resourceGroup `
--name $capacityName `
--location $location `
--sku-name $sku `
--sku-tier "PBIE_Azure" `
--administration-members "user@contoso.com"

Expected output:

{ "name": "rg-myapp-prod", "location": "eastus2", "properties": { "provisioningState": "Succeeded" } }

Terminal output for az group create

Enable auto-pause (saves cost when idle)

az powerbi embedded-capacity update `

--resource-group $resourceGroup `
--name $capacityName `
--suspend-on-idle Enabled

Write-Host "✅ Capacity created: $capacityName ($sku)" -ForegroundColor Green Write-Host "Assign workspaces to this capacity in Power BI Service settings"


## Service Principal Setup

### Create Azure AD App Registration





```powershell
## Create service principal for Power BI Embedded

$appName = "PowerBI-Embedded-App"
$tenantId = "your-tenant-id"





## Create app registration
$app = az ad app create --display-name $appName --query "{appId:appId,id:id}" -o json | ConvertFrom-Json





$appId = $app.appId
$objectId = $app.id

Write-Host "App ID (Client ID): $appId" -ForegroundColor Cyan
Write-Host "Object ID: $objectId"

## Create client secret
$secret = az ad app credential reset --id $appId --query "password" -o tsv





Write-Host "Client Secret: $secret" -ForegroundColor Yellow
Write-Host "⚠️ Save this secret securely - it won't be shown again!" -ForegroundColor Red

## Add Power BI API permissions
az ad app permission add --id $appId --api 00000009-0000-0000-c000-000000000000 --api-permissions c2f89d3e-f2a0-4c9f-a7f5-6e3e9c8b4e5f=Scope





## Grant admin consent (requires Global Admin or Application Admin)
az ad app permission admin-consent --id $appId





Write-Host "`n✅ Service Principal created successfully" -ForegroundColor Green
Write-Host "`nNext Steps:"
Write-Host "1. Enable Power BI Service Admin settings:"
Write-Host "   - Allow service principals to use Power BI APIs"
Write-Host "   - Add security group containing this service principal"
Write-Host "2. Grant service principal access to Power BI workspaces"

Power BI Service Configuration

Power BI Service Configuration

Figure: Program.cs – service registration with IntelliSense for DI lifetimes.

Enable Service Principal in Power BI Admin Portal:





1. Navigate to: https://app.powerbi.com/admin-portal
2. Tenant settings → Developer settings
3. Enable "Allow service principals to use Power BI APIs"
4. Apply to: Specific security groups
5. Create Azure AD security group:
   - Name: "PowerBI-Embedded-ServicePrincipals"
   - Add the service principal app
6. Add security group to the setting
7. Click Apply

Grant Workspace Access:

1. Open workspace in Power BI Service
2. Workspace settings → Access
3. Add service principal:
   - Search by App ID
   - Grant "Member" or "Admin" role
4. Save

Assign Workspace to Capacity:

1. Workspace settings → Premium
2. Select your Embedded capacity
3. Click Apply
4. Wait for workspace migration (1-2 minutes)

Backend Implementation (.NET)

Embed Token Generation Service

using Microsoft.PowerBI.Api;
using Microsoft.PowerBI.Api.Models;
using Microsoft.Rest;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class PowerBIEmbedService
{
```csharp
private readonly string _tenantId;
private readonly string _clientId;
private readonly string _clientSecret;
private readonly string _authorityUrl;
private readonly string _powerBIApiUrl = "https://api.powerbi.com";
private readonly string[] _scopes = new[] { "https://analysis.windows.net/powerbi/api/.default" };

public PowerBIEmbedService(string tenantId, string clientId, string clientSecret)
{
    _tenantId = tenantId;
    _clientId = clientId;
    _clientSecret = clientSecret;
    _authorityUrl = $"https://login.microsoftonline.com/{tenantId}";
}

// Get Azure AD token using service principal
private async Task<string> GetAccessTokenAsync()
{
    var clientApp = ConfidentialClientApplicationBuilder
        .Create(_clientId)
        .WithClientSecret(_clientSecret)
        .WithAuthority(new Uri(_authorityUrl))
        .Build();

    var authResult = await clientApp.AcquireTokenForClient(_scopes).ExecuteAsync();
    return authResult.AccessToken;
}

// Generate embed token for a report
public async Task<EmbedConfig> GetEmbedConfigAsync(
    Guid workspaceId, 
    Guid reportId, 
    string username = null, 
    string[] roles = null)
{
    try
    {
        // Get Azure AD token
        var accessToken = await GetAccessTokenAsync();
        var tokenCredentials = new TokenCredentials(accessToken, "Bearer");

        // Create Power BI client
        using (var client = new PowerBIClient(new Uri(_powerBIApiUrl), tokenCredentials))
        {
            // Get report details
            var report = await client.Reports.GetReportInGroupAsync(workspaceId, reportId);

            // Generate embed token
            var generateTokenRequestParameters = new GenerateTokenRequest(
                accessLevel: "View",
                datasetId: report.DatasetId
            );

            // Apply Row-Level Security (RLS) if specified
            if (!string.IsNullOrEmpty(username) && roles != null && roles.Length > 0)
            {
                var effectiveIdentity = new EffectiveIdentity(
                    username: username,
                    roles: roles,
                    datasets: new List<string> { report.DatasetId }
                );
                generateTokenRequestParameters.Identities = new List<EffectiveIdentity> { effectiveIdentity };
            }

            var tokenResponse = await client.Reports.GenerateTokenInGroupAsync(
                workspaceId, 
                reportId, 
                generateTokenRequestParameters
            );

            // Return embed configuration
            return new EmbedConfig
            {
                EmbedToken = tokenResponse.Token,
                EmbedUrl = report.EmbedUrl,
                ReportId = report.Id.ToString(),
                ReportName = report.Name,
                TokenExpiry = tokenResponse.Expiration.Value
            };
        }
    }
    catch (HttpOperationException ex)
    {
        throw new Exception($"Power BI API Error: {ex.Response.Content}", ex);
    }
}

// Generate embed token for multiple reports (dashboard scenario)
public async Task<EmbedConfig> GetEmbedConfigForMultipleReportsAsync(
    Guid workspaceId, 
    List<Guid> reportIds)
{
    var accessToken = await GetAccessTokenAsync();
    var tokenCredentials = new TokenCredentials(accessToken, "Bearer");

    using (var client = new PowerBIClient(new Uri(_powerBIApiUrl), tokenCredentials))
    {
        var reports = new List<Report>();
        var datasetIds = new List<string>();

        // Collect all reports and dataset IDs
        foreach (var reportId in reportIds)
        {
            var report = await client.Reports.GetReportInGroupAsync(workspaceId, reportId);
            reports.Add(report);
            
            if (!datasetIds.Contains(report.DatasetId))
            {
                datasetIds.Add(report.DatasetId);
            }
        }

        // Generate embed token for all reports
        var generateTokenRequestParameters = new GenerateTokenRequest(
            accessLevel: "View",
            datasetId: datasetIds[0]  // Primary dataset
        );

        // Add additional datasets
        if (datasetIds.Count > 1)
        {
            generateTokenRequestParameters.Datasets = datasetIds.Select(id => 
                new GenerateTokenRequestDataset(id)).ToList();
        }

        var tokenResponse = await client.Reports.GenerateTokenForMultipleReportsAsync(
            workspaceId,
            reportIds,
            generateTokenRequestParameters
        );

        return new EmbedConfig
        {
            EmbedToken = tokenResponse.Token,
            Reports = reports.Select(r => new EmbedReport
            {
                ReportId = r.Id.ToString(),
                ReportName = r.Name,
                EmbedUrl = r.EmbedUrl
            }).ToList(),
            TokenExpiry = tokenResponse.Expiration.Value
        };
    }
}```
}

// Data models
public class EmbedConfig
{
```text
public string EmbedToken { get; set; }
public string EmbedUrl { get; set; }
public string ReportId { get; set; }
public string ReportName { get; set; }
public DateTime TokenExpiry { get; set; }
public List<EmbedReport> Reports { get; set; }```
}

public class EmbedReport
{
```text
public string ReportId { get; set; }
public string ReportName { get; set; }
public string EmbedUrl { get; set; }```
}

API Controller Example

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using System;
using System.Threading.Tasks;

[ApiController]
[Route("api/[controller]")]
[Authorize]  // Your app's authentication
public class PowerBIController : ControllerBase
{
```csharp
private readonly PowerBIEmbedService _embedService;
private readonly IConfiguration _configuration;

public PowerBIController(PowerBIEmbedService embedService, IConfiguration configuration)
{
    _embedService = embedService;
    _configuration = configuration;
}

[HttpGet("embed-token/{reportName}")]
public async Task<IActionResult> GetEmbedToken(string reportName)
{
    try
    {
        // Get authenticated user from your app
        var currentUser = User.Identity.Name;
        
        // Map report name to workspace/report IDs (from configuration)
        var workspaceId = Guid.Parse(_configuration[$"PowerBI:Reports:{reportName}:WorkspaceId"]);
        var reportId = Guid.Parse(_configuration[$"PowerBI:Reports:{reportName}:ReportId"]);
        
        // Optional: Apply RLS based on user
        string[] roles = GetUserRolesForRLS(currentUser);
        
        // Generate embed configuration
        var embedConfig = await _embedService.GetEmbedConfigAsync(
            workspaceId,
            reportId,
            currentUser,
            roles
        );
        
        return Ok(embedConfig);
    }
    catch (Exception ex)
    {
        return StatusCode(500, new { error = ex.Message });
    }
}

private string[] GetUserRolesForRLS(string username)
{
    // Your logic to determine RLS roles
    // Example: User's department, region, or customer ID
    return new[] { "SalesRegion:East" };
}```
}

Frontend Implementation (React)

Power BI React Component

import React, { useEffect, useRef, useState } from 'react';
import { models, service, factories } from 'powerbi-client';

interface PowerBIEmbedProps {
  reportName: string;
  filters?: models.IFilter[];
  onLoaded?: () => void;
  onError?: (error: any) => void;
}

const PowerBIEmbed: React.FC<PowerBIEmbedProps> = ({ 
  reportName, 
  filters, 
  onLoaded, 
  onError 
}) => {
  const reportContainer = useRef<HTMLDivElement>(null);
  const [report, setReport] = useState<service.Report | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
```javascript
embedReport();
return () => {
  // Cleanup: remove report on unmount
  if (report) {
    report.off('loaded');
    report.off('error');
    factories.powerBIReportFactory.remove(reportContainer.current!);
  }
};```
  }, [reportName]);

  const embedReport = async () => {
```python
try {
  setLoading(true);
  setError(null);

  // Fetch embed configuration from backend
  const response = await fetch(`/api/powerbi/embed-token/${reportName}`, {
    headers: {
      'Authorization': `Bearer ${localStorage.getItem('authToken')}`
    }
  });

  if (!response.ok) {
    throw new Error(`Failed to get embed token: ${response.statusText}`);
  }

  const embedConfig = await response.json();

  // Power BI embed configuration
  const config: models.IReportEmbedConfiguration = {


    type: 'report',
    tokenType: models.TokenType.Embed,
    accessToken: embedConfig.embedToken,
    embedUrl: embedConfig.embedUrl,
    id: embedConfig.reportId,
    settings: {
      panes: {
        filters: {
          expanded: false,
          visible: true
        },
        pageNavigation: {
          visible: true
        }
      },
      background: models.BackgroundType.Transparent,
      layoutType: models.LayoutType.Custom,
      customLayout: {
        displayOption: models.DisplayOption.FitToWidth
      }
    },
    filters: filters || []
  };

  // Embed report
  const powerbi = new service.Service(
    factories.hpmFactory,
    factories.wpmpFactory,
    factories.routerFactory
  );

  const embeddedReport = powerbi.embed(
    reportContainer.current!,
    config
  ) as service.Report;

  // Handle loaded event
  embeddedReport.on('loaded', () => {
    console.log('Report loaded successfully');
    setLoading(false);
    onLoaded?.();
  });

  // Handle error event
  embeddedReport.on('error', (event) => {
    const errorDetail = event.detail;
    console.error('Report error:', errorDetail);
    setError(errorDetail.message);
    setLoading(false);
    onError?.(errorDetail);
  });

  // Handle data selected event (optional)
  embeddedReport.on('dataSelected', (event) => {
    console.log('Data selected:', event.detail);
  });

  setReport(embeddedReport);

  // Refresh token before expiry
  scheduleTokenRefresh(embedConfig.tokenExpiry, embeddedReport);

} catch (err: any) {
  console.error('Embedding error:', err);
  setError(err.message);
  setLoading(false);
  onError?.(err);
}```
  };

  const scheduleTokenRefresh = (tokenExpiry: string, embeddedReport: service.Report) => {
```javascript
const expiryTime = new Date(tokenExpiry).getTime();
const currentTime = Date.now();
const timeUntilExpiry = expiryTime - currentTime;

// Refresh token 5 minutes before expiry
const refreshTime = timeUntilExpiry - (5 * 60 * 1000);

if (refreshTime > 0) {
  setTimeout(async () => {
    try {
      // Fetch new token
      const response = await fetch(`/api/powerbi/embed-token/${reportName}`);
      const embedConfig = await response.json();

      // Update token
      await embeddedReport.setAccessToken(embedConfig.embedToken);
      console.log('Token refreshed successfully');

      // Schedule next refresh
      scheduleTokenRefresh(embedConfig.tokenExpiry, embeddedReport);
    } catch (err) {
      console.error('Token refresh failed:', err);
    }
  }, refreshTime);
}```
  };

  const applyFilters = async (newFilters: models.IFilter[]) => {
```javascript
if (report) {
  try {
    await report.updateFilters(models.FiltersOperations.Replace, newFilters);
    console.log('Filters applied successfully');
  } catch (err) {
    console.error('Failed to apply filters:', err);
  }
}```
  };

  return (
```text
<div style={{ position: 'relative', width: '100%', height: '600px' }}>
  {loading && (
    <div style={{ 
      position: 'absolute', 
      top: '50%', 
      left: '50%', 
      transform: 'translate(-50%, -50%)' 
    }}>
      Loading report...
    </div>
  )}
  {error && (
    <div style={{ color: 'red', padding: '20px' }}>
      Error: {error}
    </div>
  )}
  <div 
    ref={reportContainer} 
    style={{ width: '100%', height: '100%' }}
  />
</div>```
  );
};

export default PowerBIEmbed;

Usage Example

// App.tsx
import React from 'react';
import PowerBIEmbed from './components/PowerBIEmbed';
import { models } from 'powerbi-client';

const App: React.FC = () => {
  const handleReportLoaded = () => {
```javascript
console.log('Sales report loaded successfully');```
  };

  const handleReportError = (error: any) => {
```text
console.error('Sales report error:', error);
// Send to monitoring service```
  };

  // Apply filters programmatically
  const filters: models.IBasicFilter[] = [
```json
{
  $schema: "http://powerbi.com/product/schema#basic",
  target: {
    table: "Sales",
    column: "Region"
  },
  operator: "In",
  values: ["East", "West"],
  filterType: models.FilterType.Basic
}```
  ];

  return (
```text
<div className="App">
  <h1>Sales Dashboard</h1>
  <PowerBIEmbed
    reportName="sales-overview"
    filters={filters}
    onLoaded={handleReportLoaded}
    onError={handleReportError}
  />
</div>```
  );
};

export default App;

Multi-Tenant Architecture

Strategy 1: Workspace-Per-Tenant

Architecture: Each tenant gets dedicated workspace

Pros:
✅ Complete data isolation
✅ Tenant-specific customization
✅ Independent refresh schedules
✅ Easy to add/remove tenants

Cons:
❌ Higher management overhead
❌ Workspace limits (1000 per capacity)
❌ More complex deployment

Use Case: SaaS with <100 customers, strong isolation requirements

public class MultiTenantWorkspaceService
{
```csharp
private readonly PowerBIEmbedService _embedService;
private readonly Dictionary<string, TenantWorkspace> _tenantWorkspaces;

public async Task<EmbedConfig> GetTenantReportAsync(string tenantId, string reportType)
{
    // Look up tenant's dedicated workspace
    if (!_tenantWorkspaces.ContainsKey(tenantId))
    {
        throw new Exception($"Workspace not found for tenant: {tenantId}");
    }

    var workspace = _tenantWorkspaces[tenantId];
    var reportId = workspace.Reports[reportType];

    // Generate embed token (no RLS needed - workspace is already isolated)
    return await _embedService.GetEmbedConfigAsync(workspace.WorkspaceId, reportId);
}

public async Task ProvisionTenantWorkspaceAsync(string tenantId, string tenantName)
{
    // Create new workspace for tenant
    // Deploy reports from template
    // Assign to capacity
    // Update tenant mapping
}```
}

public class TenantWorkspace
{
```text
public Guid WorkspaceId { get; set; }
public string TenantId { get; set; }
public Dictionary<string, Guid> Reports { get; set; }```
}

Strategy 2: Shared Workspace with RLS

Architecture: Single workspace, Row-Level Security for data isolation

Pros:
✅ Lower management overhead
✅ Scales to thousands of tenants
✅ Single deployment/updates
✅ Efficient capacity usage

Cons:
❌ Complex RLS configuration
❌ All tenants impacted by single tenant's issues
❌ Shared refresh schedule

Use Case: SaaS with 100+ customers, standardized reports

public class MultiTenantRLSService
{
```csharp
private readonly PowerBIEmbedService _embedService;
private readonly Guid _sharedWorkspaceId = Guid.Parse("shared-workspace-guid");

public async Task<EmbedConfig> GetTenantReportAsync(string tenantId, Guid reportId)
{
    // Apply RLS with tenant identifier
    var username = $"tenant:{tenantId}";
    var roles = new[] { "TenantUser" };

    return await _embedService.GetEmbedConfigAsync(
        _sharedWorkspaceId,
        reportId,
        username,
        roles
    );
}```
}

RLS DAX Example:

// In Power BI Desktop: Modeling → Manage Roles → Create "TenantUser" role

// RLS Rule on Sales table:
[TenantID] = RIGHT(USERNAME(), LEN(USERNAME()) - 7)

// USERNAME() returns "tenant:12345"
// RIGHT() extracts "12345"
// Only rows matching TenantID are visible

Strategy 3: Hybrid Approach

Architecture: Premium tenants get dedicated workspace, others share with RLS

Pros:
✅ Flexibility for different SLA tiers
✅ Balance scalability and isolation
✅ Upsell opportunity (dedicated workspace as premium feature)

Cons:
❌ Most complex to manage
❌ Two code paths to maintain

Use Case: Freemium/tiered SaaS products

Monitoring and Operations

Capacity Monitoring Script

## Monitor Power BI Embedded capacity utilization

function Get-EmbeddedCapacityMetrics {

> **Architecture Overview:** param(


**Expected output:**

```text
{ "value": [{ "name": { "value": "Requests" }, "timeseries": [{ "data": [{ "total": 1234 }] }] }] }

Terminal output for az monitor

Application Insights Integration

Application Insights Integration

Figure: Workspace – published reports, datasets, and app installation dialog.

using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;





public class EmbeddedAnalyticsLogger
{
```text
private readonly TelemetryClient _telemetry;

public EmbeddedAnalyticsLogger(TelemetryClient telemetry)
{
    _telemetry = telemetry;
}

public void LogEmbedTokenGenerated(string reportName, string tenantId, string userId)
{
    var telemetry = new EventTelemetry("EmbedTokenGenerated");
    telemetry.Properties["ReportName"] = reportName;
    telemetry.Properties["TenantId"] = tenantId;
    telemetry.Properties["UserId"] = userId;
    telemetry.Properties["Timestamp"] = DateTime.UtcNow.ToString("o");
    
    _telemetry.TrackEvent(telemetry);
}

public void LogReportLoadTime(string reportName, double loadTimeMs)
{
    _telemetry.TrackMetric("ReportLoadTime", loadTimeMs, new Dictionary<string, string>
    {
        { "ReportName", reportName }
    });
}

public void LogEmbedError(string reportName, Exception ex, string userId)
{
    _telemetry.TrackException(ex, new Dictionary<string, string>
    {
        { "ReportName", reportName },
        { "UserId", userId },
        { "ErrorType", "EmbedFailure" }
    });
}```
}

Security Best Practices

Security Best Practices

Figure: RLS role editor – DAX filter expression and testing panel.

☑ Token Management:
  ☐ Generate tokens server-side only (never client-side)
  ☐ Use minimum token lifetime needed (default: 60 min)
  ☐ Implement token refresh before expiry
  ☐ Never log or expose embed tokens
  ☐ Rotate client secrets every 90 days





☑ Service Principal:
  ☐ Use dedicated service principal per environment
  ☐ Store credentials in Azure Key Vault
  ☐ Grant minimum required permissions
  ☐ Monitor service principal activity
  ☐ Use managed identity where possible

☑ Row-Level Security:
  ☐ Always implement RLS for multi-tenant scenarios
  ☐ Test RLS with each tenant identity
  ☐ Use dynamic RLS based on USERNAME()
  ☐ Audit RLS rules quarterly
  ☐ Document RLS logic clearly

☑ Network Security:
  ☐ Use HTTPS only for all API calls
  ☐ Implement CORS policies restrictively
  ☐ Whitelist Power BI domains in CSP headers
  ☐ Enable Azure Private Link (for Premium)
  ☐ Monitor unusual access patterns

☑ Application Security:
  ☐ Authenticate users in your application first
  ☐ Validate user permissions before generating tokens
  ☐ Implement rate limiting on embed token endpoints
  ☐ Log all embed token requests
  ☐ Alert on failed authorization attempts

Troubleshooting Guide

Issue 1: "Failed to get embed token - 401 Unauthorized"

Diagnosis:

## Test service principal authentication

$tenantId = "your-tenant-id"
$clientId = "your-client-id"
$clientSecret = "your-client-secret"





$body = @{
```text
grant_type = "client_credentials"
client_id = $clientId
client_secret = $clientSecret
resource = "https://analysis.windows.net/powerbi/api"```
}

$response = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$tenantId/oauth2/token" -Body $body

if ($response.access_token) {
```text
Write-Host "✅ Authentication successful" -ForegroundColor Green```
} else {
```text
Write-Host "❌ Authentication failed" -ForegroundColor Red```
}

Common Causes:

  1. Service principal not enabled in Power BI tenant settings
  2. Client secret expired
  3. Service principal doesn't have workspace access
  4. Incorrect tenant ID

Resolution:

  • Verify tenant settings enable service principals
  • Generate new client secret if expired
  • Grant workspace Member/Admin role to service principal
  • Double-check tenant ID in code

Issue 2: "Report fails to load - blank iframe"

Diagnosis:

// Check browser console for errors
// Common errors:
// - CORS policy blocking
// - Content Security Policy (CSP) violation
// - Invalid embed token
// - Workspace not assigned to capacity

Resolutions:

// 1. Verify CORS headers on backend API
app.UseCors(policy => policy
```text
.WithOrigins("https://your-app.com")
.AllowAnyMethod()
.AllowAnyHeader());

> **Architecture Overview:** 2. Add Power BI domains to CSP headers


### Issue 3: RLS not filtering data correctly

**Diagnosis:**


> **Architecture Overview:** Test RLS in Power BI Desktop


**Common Issues:**

- USERNAME() format doesn't match expectation
- RLS role not selected during token generation
- Multiple RLS rules conflicting
- Case sensitivity in comparisons


**Resolution:**

- Standardize username format (e.g., "tenant:12345")
- Always specify roles in GenerateTokenRequest
- Use UPPER() or LOWER() for case-insensitive comparisons
- Test RLS with each tenant's actual data


## Best Practices Summary


![Best Practices Summary](/images/articles/power-bi/2025-09-15-embedded-analytics-power-bi-embedded-implementation-ctx-4.svg)

*Figure: Configuration and management dashboard with status overview.*


> **Architecture Overview:** ☑ Architecture:


## Architecture Decision and Tradeoffs

When designing business intelligence solutions with Power BI, consider these key architectural trade-offs:

| Approach | Best For | Tradeoff |
|----------|----------|----------|
| Managed / platform service | Rapid delivery, reduced ops burden | Less customisation, potential vendor lock-in |
| Custom / self-hosted | Full control, advanced tuning | Higher operational overhead and cost |

> **Recommendation:** Start with the managed approach for most workloads and move to custom only when specific requirements demand it.

## Validation and Versioning

- Last validated: April 2026
- Validate examples against your tenant, region, and SKU constraints before production rollout.
- Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.

## Security and Governance Considerations

- Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
- Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
- Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.

## Cost and Performance Notes

- Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
- Baseline performance with synthetic and real-user checks before and after major changes.
- Scale resources with measured thresholds and revisit sizing after usage pattern changes.

## Official Microsoft References

- https://learn.microsoft.com/power-bi/
- https://learn.microsoft.com/power-bi/guidance/
- https://learn.microsoft.com/fabric/

## Public Examples from Official Sources

- These examples are sourced from official public Microsoft documentation and sample repositories.
- Documentation examples: https://learn.microsoft.com/power-bi/
- Sample repositories: https://github.com/microsoft/PowerBI-Developer-Samples
- Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.

## Key Takeaways

- **Service Principal authentication** is the production-standard approach (not master user)
- **Capacity planning** requires understanding concurrent users, reports, and query patterns
- **Multi-tenancy** strategy choice (workspace-per-tenant vs. RLS) depends on scale and isolation requirements
- **Token management** must handle generation, refresh, and expiry gracefully
- **Frontend integration** requires powerbi-client.js library and proper event handling
- **Monitoring** capacity utilization prevents performance issues and cost overruns
- **Security** layers include Azure AD, service principal permissions, RLS, and token lifetime limits
- **Auto-pause** can save 70%+ on costs for non-24/7 workloads
- **Testing RLS** is critical—always validate with actual tenant identities
- **Documentation** of tenant mappings, RLS logic, and deployment procedures prevents operational issues





## Next Steps

1. **Provision Azure resources** (capacity, service principal, Key Vault)
2. **Enable service principals** in Power BI tenant settings
3. **Implement backend service** for token generation (.NET/Node.js)
4. **Build frontend component** with powerbi-client.js (React/Angular/Vue)
5. **Configure RLS** for multi-tenant data isolation
6. **Set up monitoring** (Application Insights, capacity alerts)
7. **Test thoroughly** (token refresh, RLS, error handling, performance)
8. **Document architecture** (tenant mapping, security model, runbooks)
9. **Deploy to production** with gradual rollout
10. **Monitor and optimize** (capacity utilization, user experience, costs)


## Additional Resources

- [Power BI Embedded Documentation](https://learn.microsoft.com/power-bi/developer/embedded/)
- [Power BI REST API Reference](https://learn.microsoft.com/rest/api/power-bi/)
- [Power BI JavaScript API](https://learn.microsoft.com/javascript/api/overview/powerbi/)
- [Embed Token Generation](https://learn.microsoft.com/power-bi/developer/embedded/embed-tokens)
- [Row-Level Security](https://learn.microsoft.com/power-bi/enterprise/service-admin-rls)
- [Capacity Planning](https://learn.microsoft.com/power-bi/developer/embedded/embedded-capacity-planning)
- [Multi-Tenancy Patterns](https://learn.microsoft.com/power-bi/developer/embedded/embed-multi-tenancy)
- [Power BI Embedded Pricing](https://azure.microsoft.com/pricing/details/power-bi-embedded/)


---

*Embed intelligence. Scale seamlessly. Secure completely.*

Discussion