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" } }
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
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 }] }] }] }
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
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:
- Service principal not enabled in Power BI tenant settings
- Client secret expired
- Service principal doesn't have workspace access
- 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

*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