I am tring to authenticate PowerBI report via an embed access code, I am using NEXJS, the authentication is happening on the server side, the client call the server api to get the access embed token.The idea is to just display the power BI report in our web app. I do not want to use the public embed link.
Setup that's already done :
- Creation of application in azure AD and adding API Permissions. [Reports.ReadWrite.All,Workspace.ReadWrite.All,Dataset.ReadWrite.All]
- On the client side I am using
powerbi-client-reactto render the powerbi report. - In Power BI (Admin) - enabled Service principals can use Fabric APIs &
// server side
import { ConfidentialClientApplication } from "@azure/msal-node";
export async function confidentialClientApplication() {
const config = {
auth: {
clientId: "<client>",
authority: "https://login.microsoftonline.com/<tenant_id>",
clientSecret: "<client_secret>",
},
};
return new ConfidentialClientApplication(config);
}
export async function getAccessToken() {
try {
const cca = await confidentialClientApplication();
const authResponse = await cca.acquireTokenByClientCredential({
scopes: ["https://analysis.windows.net/powerbi/api/.default"],
});
return authResponse.accessToken;
} catch (error) {
console.error("Error getting access token:", error);
throw new Error("Failed to get access token");
}
}
export default async function handler(req, res) {
if (req.method === "POST") {
try {
const accessToken = await getAccessToken();
const { reportId, groupId, userEmail, accessLevel } = req.body;
const embedTokenResponse = await fetch(
`https://api.powerbi.com/v1.0/myorg/groups/${groupId}/reports/${reportId}/GenerateToken`,
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ accessLevel: "View", reportId }),
}
);
if (!embedTokenResponse.ok) {
console.log(embedTokenResponse);
throw new Error("Failed to retrieve embed token");
}
const { embedToken } = await embedTokenResponse.json();
res.status(200).json({ embedToken });
} catch (error) {
res.status(500).json({ error: "Internal server error" });
}
} else {
res.status(405).json({ error: "Method not allowed" });
}
}
// client side
import React, { useEffect } from "react";
import { PowerBIEmbed } from "powerbi-client-react";
import { models } from "powerbi-client";
function PowerBIReport() {
useEffect(() => {
const fetchEmbedToken = async () => {
const response = await fetch("/api/powerbi", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
reportId: "<report_id>",
groupId: "<group_id>",
userEmail: "<internal_user_email_id>", // also powerbi admin user
accessLevel: "View",
}),
});
if (response.ok) {
const { embedToken } = await response.json();
console.log(embedToken);
}
};
fetchEmbedToken();
}, []);
return (
<PowerBIEmbed
embedConfig={{
type: "report", // Supported types: report, dashboard, tile, visual, qna and paginated report
id: "<report_id>",
embedUrl:
"https://app.powerbi.com/reportEmbed?reportId=<report_id>&ctid=<ctid_id>",
accessToken: "", // wanting to pass the embed access token there
tokenType: models.TokenType.Embed,
}}
/>
);
}
export default PowerBIReport;
The getAccessToken function provides me the accessToken without any issue. The issue arises when i use the received accessToken to embedTokenResponse to make the api request to get embedToken. The error I get is "Error: Failed to retrieve embed token" error 401. I have verified that the provided groupId & reportId is indeed correct (taken from power bi itself).
I have also tried to change the url:
https://api.powerbi.com/v1.0/myorg/groups/${groupId}/reports/${reportId}/GenerateToken - POST
https://api.powerbi.com/v1.0/myorg/groups/${groupId}/reports/${reportId} - GET
I am not sure exactly what is wrong and what is missing, any help in order to fix this issue is greatly appreciated.