ARCHININGEN TECH BLOG
← 記事一覧へ戻る

Power BI Embedded 入門 — Managed Identity でシークレットレスにレポートを埋め込む

CONTENTS

Power BI Embedded の App owns data 構成におけるトークンの流れ図。左上に Node.js バックエンド (Azure 上、system-assigned Managed Identity を保持)、右上に Microsoft Entra ID、右下に Power BI のワークスペース (レポート + セマンティックモデル)、左下にブラウザ (embed トークンだけを保持) が配置される。矢印は 4 本: (1) バックエンドが Managed Identity で Entra ID から AAD トークンを取得、(2) GenerateToken v2 を呼び embed トークンが返る、(3) バックエンドはブラウザへ embed トークンだけを返す、(4) ブラウザが embed トークンでレポートを描画する。AAD トークンはフロントへ渡さないことが破線の遮断記号で示されている。

「自社の Web アプリの画面に、Power BI で作ったレポートをそのまま出したい」という要件は、BI がある程度定着した組織なら遅かれ早かれ降ってきます。厄介なのは閲覧者です。社外の顧客やパートナーで、当然 Power BI のライセンスは持っていません。

Power BI 標準の共有機能は「閲覧者も Power BI ユーザーである」ことが前提で、ライセンスのない相手には使えません。かといって「Web に公開 (publish to web)」は認証なしの全世界公開なので、業務データでは論外です。この隙間を埋めるのが App owns data (顧客向け埋め込み) と呼ばれる構成で、アプリ側が発行した短命の埋め込みトークンでレポートを表示するため、閲覧者は Power BI のアカウントもライセンスも必要ありません。

本記事では、この App owns data 構成で最初の 1 レポートを自社アプリに表示するまでを通しで組みます。ベースは筆者が実環境 (Azure Container Apps 上の Node.js アプリ) で採用している構成ですが、コードはフレームワークに依存しない Node.js で書きます。特徴は認証を Managed Identity に寄せている点で、公式チュートリアルの定番であるクライアントシークレットをランタイムから排除します。

RLS (行レベルセキュリティ) によるテナント分離、社内ユーザー向けの user owns data 構成、REST API での生データ取得、容量の詳細なサイジングは本記事のスコープ外とします。

App owns data の全体像 — 2 種類のトークン

Power BI の埋め込みには大きく 2 つのシナリオがあります。

App owns data (顧客向け) User owns data (組織向け)
閲覧者 社外ユーザー・ライセンス不要 社内の Power BI ユーザー
Power BI へのサインイン しない (アプリの認証だけ) Entra ID で各自サインイン
トークン アプリが発行する embed トークン 閲覧者本人の AAD トークン
ライセンス費用 アプリ側が容量を購入 閲覧者が各自ライセンス保有

外部の顧客に見せるなら前者一択です。そして App owns data の設計は、冒頭の図のとおりトークンの 2 段構えとして理解するのが早道です。

1 段目は AAD トークン。バックエンドが Entra ID から取得する、Power BI REST API を呼ぶための資格情報です。ワークスペース内の広い操作ができるため、サーバーから一歩も外に出してはいけません。2 段目が embed トークン。AAD トークンを使って GenerateToken API で発行する、「このレポートをこの条件で表示する」ことしかできない限定的なトークンです。フロントエンドに渡してよいのはこちらだけです。

この役割分担さえ守れば、フロントに秘密は何も置かれません。以降の手順はすべて、この原則を崩さずに組み立てていきます。

もうひとつ、大前提を明記しておきます。閲覧者が Power BI のライセンスと認証を免除されるのは、代わりに自社アプリが閲覧者を認証しているからです。「ライセンス不要」は「認証不要」ではありません。埋め込みページと embed トークンを返す API は、必ず自社で用意した認証機構 (ログイン) の内側に置いてください。ここを認証なしで公開すると、embed トークンが誰にでも発行される — つまり冒頭で論外とした「Web に公開」と実質同じ全世界公開になり、この構成を選んだ意味がなくなります。

なぜ Managed Identity か

公式チュートリアルの定番は、Entra ID にアプリを登録してサービスプリンシパル + クライアントシークレットで AAD トークンを取る方法です。これでも動きますが、シークレットという秘密が生まれた瞬間から、保管場所 (Key Vault 等)・ローテーション・有効期限切れによる本番停止、という運用が一生ついて回ります。

アプリを Azure 上で動かすなら、Managed Identity (MI) でこの運用を丸ごと消せます。Container Apps や App Service に system-assigned MI を付与すると、Azure が管理する身元でトークンが取れるようになり、シークレットの発行・保管という工程そのものが存在しなくなります。コードは @azure/identityDefaultAzureCredential を使うだけで、ローカル開発時は az login の資格情報に自動フォールバックするため、環境ごとの分岐も不要です。

押さえておきたいのは、MI の実体も Entra ID 上ではサービスプリンシパルの一種だという点です。Power BI 側から見れば「サービスプリンシパルによる API 利用」と同じ経路を通るため、後述するテナント設定やワークスペース権限の手順は共通です。Azure 以外でホスティングしている場合は、本記事の手順のうち MI の部分をアプリ登録 + シークレットに読み替えれば、残りはそのまま使えます。

筆者は Container Apps の system-assigned MI を採用し、Key Vault にも Power BI 用のシークレットを置いていません。漏れる物がなければ、漏洩対策も要らないという判断です。

容量と開発環境の選び方 (2026 年 8 月時点)

本番で埋め込みを動かすには専用の容量 (capacity) が必要です。ここ数年で製品側の動きが激しかったので、現状を整理しておきます。

SKU 現状 購入経路・課金 コスト感 (東日本・従量)
A SKU (Power BI Embedded) 現役。A1〜A8 Azure リソースとして時間課金。pause 可 A1 約 $1.0/時 (≈ $740/月)、A4 約 $8.1/時
F SKU (Microsoft Fabric) 現役。F2〜 Azure リソースとして時間課金。pause 可、予約割引あり F2 約 $0.42/時 (≈ $310/月)、F64 約 $13.4/時
P SKU (Power BI Premium) 廃止 (2024 年 7 月に新規購入終了)

P SKU の廃止に続き、2024 年には A SKU も Fabric の F SKU へ統合して廃止する計画が発表されましたが、こちらは顧客の反発を受けて撤回されています。A SKU は現在も新規作成できます。

コスト感も押さえておきます (2026 年 8 月時点・東日本リージョンの概算)。性能の対応関係は A1 ≈ F8、A4 ≈ F64 です。同性能帯で比べると、A1 が約 $1.0/時 (24 時間稼働で月 $740 前後) に対し F8 は約 $1.7/時 (月 $1,200 強) で、A SKU の方が 4 割ほど安くつきます。F SKU の単価は Data Factory・Warehouse・OneLake まで含む「Fabric 全部入り」の値付けなので、レポートの埋め込み表示にしか使わないなら割高です。顧客向け埋め込みがメインなら、素直に Embedded (A SKU) を選ぶのが筆者の結論です。最小サイズの絶対額だけは F2 (約 $0.42/時) が A1 を下回りますが、容量は A1 の 1/4 相当です。なお本番の容量は止めない前提になるため (理由は後述)、コストは時間単価ではなく 24 時間稼働の月額固定費として見積もるのが実態に合います。月額前提なら、F を 1 年予約して約 4 割引 (A の従量とほぼ同水準) にする選択肢も出てきます。価格は改定・為替で動くため、確定前に Azure の価格ページで最新値を確認してください。

筆者は Azure ポータルから Power BI Embedded リソース (A SKU) を作成して本番の容量にしています。時間課金なので pause でいくらでも節約できそうに見えますが、本番容量の pause は現実的ではありません。止めた瞬間、顧客からレポートが見えなくなるからです。pause が素直に効くのは開発・検証用の容量までで、本番は止めない前提の月額固定費として予算化します。

サイズ選定も同じ発想で考えます。小さく始められるのは開発時の利点であって、本番で複数のレポートを載せ、セマンティックモデルのスケジュール更新を頻繁に回すなら、現実的なサイズは A4 ≈ F64 クラス以上を見ておくべきというのが筆者の感覚です。容量が足りないと、まずスケジュール更新の失敗や表示の遅延として顕在化します。コストを意識するあまり更新エラーでレポートが古いまま、では元も子もありません。ここは削る場所ではなく、あらかじめ予算として見越しておく場所です。

セットアップ手順

前提は次のとおりです。

  • Azure サブスクリプションと、MI を付与できるコンピューティング (Container Apps / App Service 等)
  • Power BI テナントの管理者権限 (テナント設定を変更できること)
  • レポートを発行できる Pro 相当のユーザー (個人試用版で可)
  • 容量は初日には不要 (開発中は GenerateToken が無料の試用トークンを返す。購入は本番移行時で足りる)

手順は 6 ステップです。経験上、最大の関門はステップ 2〜3 の権限設定で、コードより先にここで躓きます。ハマったら後述の躓きポイントへ飛んでください。

1. Managed Identity を有効化する

まずアプリのコンピューティングに system-assigned MI を付与します。

BASH
# Container Apps に system-assigned MI を付与し、principalId を控える
az containerapp identity assign \
  --name <app-name> \
  --resource-group <resource-group> \
  --system-assigned \
  --query principalId --output tsv

出力される principalId が、Entra ID 上でこのアプリを指す ID です。次のステップで使うので控えておきます。App Service なら az webapp identity assign で同様です。

2. セキュリティグループを作り、テナント設定で許可する

ここが最初の非自明ポイントです。Managed Identity は Power BI のワークスペースに直接追加できません。Power BI サービスの UI が MI 単体をメンバーとして指定できない仕様のため、Entra ID のセキュリティグループを介在させます。

BASH
# セキュリティグループを作成し、MI をメンバーに入れる
az ad group create \
  --display-name powerbi-embed-apps --mail-nickname powerbi-embed-apps
az ad group member add \
  --group powerbi-embed-apps --member-id <principalId>

続いて Power BI (Fabric) 管理ポータルのテナント設定で、開発者設定にある「サービス プリンシパルによる API の使用を許可」を有効化します。対象は「組織全体」ではなく、いま作ったセキュリティグループに限定するのが定石です。MI もサービスプリンシパルとしてこの設定の対象になります。あわせて「アプリでのコンテンツの埋め込み」も有効になっていることを確認してください。

テナント設定とグループメンバーシップの反映には最大 15 分程度かかります。この待ち時間を知らないと、正しい設定を「効いていない」と誤認して壊してしまいます。

3. ワークスペースに Member ロールで追加する

表示したいレポートをワークスペースへ発行したら (Pro 試用版のユーザーで可)、ワークスペースの「アクセスの管理」から先ほどのセキュリティグループを追加します。ロールは Member (メンバー) 以上にしてください。筆者の環境では、Contributor (共同作成者) では埋め込みトークンの発行が通りませんでした。

ここまでの権限設定が通っているかは、コードを書く前に REST API で確認できます。

BASH
# 疎通確認: レポート一覧が 200 で返れば権限設定は通っている
TOKEN=$(az account get-access-token \
  --resource https://analysis.windows.net/powerbi/api \
  --query accessToken --output tsv)
curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer $TOKEN" \
  "https://api.powerbi.com/v1.0/myorg/groups/<workspaceId>/reports"

なおこの確認は az login 中のユーザーの権限で通ります。MI 本体での疎通は、デプロイ後に同じ API をアプリから呼んで確認します (ステップ 4 以降のコードがそのまま確認手段になります)。

4. Managed Identity で AAD トークンを取得する

ここからバックエンドのコードです。@azure/identityDefaultAzureCredential を使います。

JAVASCRIPT
// AAD トークン取得 — credential はモジュールスコープで 1 インスタンスを使い回す
import { DefaultAzureCredential } from "@azure/identity";

const POWERBI_SCOPE = "https://analysis.windows.net/powerbi/api/.default";
const credential = new DefaultAzureCredential({
  tenantId: process.env.AZURE_TENANT_ID,
});

export async function getAadToken() {
  const { token } = await credential.getToken(POWERBI_SCOPE);
  return token;
}

短いコードですが 2 点、実運用で効く設計判断が入っています。まず credential をリクエストごとに new せず使い回すこと。DefaultAzureCredential は内部にトークンキャッシュを持っており、毎回生成するとキャッシュが効かず、MI のトークン取得エンドポイントへ都度往復してスロットリングを招きます。次に tenantId を明示すること。ローカルの az login が別テナントを向いていた場合に、意図しないテナントでのトークン発行を防げます。

5. GenerateToken v2 で埋め込みトークンを発行する

埋め込みトークンの発行 API には新旧 2 系統あります。レポート単位の古い v1 (/reports/{id}/GenerateToken) ではなく、v2 (POST /v1.0/myorg/GenerateToken) を使ってください。対象のレポート・データセット・ワークスペースを body で明示する方式で、複数ワークスペース運用にもそのまま伸ばせます。

v2 は body に datasetId が必須ですが、筆者は datasetId を DB 等に保存せず、発行のたびにレポートのメタ情報から解決しています。PBIX を差し替えると datasetId が変わることがあり、保存してしまうと差し替えのたびに設定更新が必要になるためです。保存するのは Workspace ID と Report ID だけで足ります。

JAVASCRIPT
// レポートのメタ情報から embedUrl と datasetId を毎回解決する
const BASE = "https://api.powerbi.com/v1.0/myorg";

async function getReportMeta(aadToken, workspaceId, reportId) {
  const res = await fetch(`${BASE}/groups/${workspaceId}/reports/${reportId}`, {
    headers: { Authorization: `Bearer ${aadToken}` },
  });
  if (!res.ok) throw new Error(`report meta: HTTP ${res.status}`);
  return res.json(); // embedUrl と datasetId を含む
}

embedUrl もここで取れる値を使います (ハードコードしない)。続いて本命の発行です。

JAVASCRIPT
// GenerateToken v2 — 表示専用 (allowEdit: false) の embed トークンを発行する
async function generateEmbedToken(aadToken, workspaceId, reportId, datasetId) {
  const res = await fetch(`${BASE}/GenerateToken`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${aadToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      reports: [{ id: reportId, allowEdit: false }],
      datasets: [{ id: datasetId }],
      targetWorkspaces: [{ id: workspaceId }],
    }),
  });
  if (!res.ok) throw new Error(`GenerateToken: HTTP ${res.status}`);
  return res.json(); // token と expiration が返る
}

この 2 つを束ねて、アプリの認証を通ったユーザーにだけ { token, embedUrl, reportId } を返すエンドポイントを 1 本立てれば、バックエンドは完成です。全体像の章に書いたとおり、このエンドポイントを認証ガードなしで公開してはいけません。セッションやトークン検証など、自社アプリの既存の認証チェックを必ずこのエンドポイントの手前に挟みます。embed トークンはサーバー側でキャッシュせず都度発行にしています。短命な資格情報は「使い捨てる」方が、キャッシュの失効管理を持ち込むより安全でシンプルです。

6. powerbi-client でレポートを表示する

フロントエンドは Microsoft 公式の powerbi-client を使います。

JAVASCRIPT
// フロント側 — サーバーから受け取った embed トークンだけで描画する
import { service, factories, models } from "powerbi-client";

const powerbi = new service.Service(
  factories.hpmFactory, factories.wpmpFactory, factories.routerFactory);
const { token, embedUrl, reportId } =
  await fetch("/api/powerbi/embed-token").then((r) => r.json());

powerbi.embed(document.getElementById("report-container"), {
  type: "report",
  tokenType: models.TokenType.Embed,
  accessToken: token,
  embedUrl,
  id: reportId,
});

tokenType は必ず models.TokenType.Embed です。ブラウザでページを開き、レポートが描画されれば完走です。容量なしの開発段階なら、レポート上部に「Free trial version」のバナーが出ているはずです — それが「試用トークンで正しく動いている」印です。

躓きポイント

Managed Identity がワークスペースに追加できない

ステップ 2 に書いたとおり、ワークスペースの「アクセスの管理」で MI を直接検索しても出てきません。仕様です。セキュリティグループを作って MI を入れ、グループを追加してください。「MI が検索に出ない = 何か設定を間違えた」と思って MI を作り直すのが典型的な空振りです。

Member 必須 — Contributor では発行できない

ワークスペースのロールを最小権限にしようと Contributor を選ぶと、レポートのメタ情報は読めるのに GenerateToken だけが失敗する、という中途半端な状態になります。埋め込みトークンの発行には Member 以上が必要です。「読むだけなのだから低いロールでよいはず」という直感が通らないポイントです。

設定変更は「作り直す前にまず待つ」

テナント設定・セキュリティグループのメンバー追加・ワークスペースのロール変更は、いずれも反映まで最大 15 分程度のラグがあります。設定直後の失敗は設定ミスと区別がつきません。変更したら 15 分待ってから再試行し、それでも失敗するときだけ設定を疑う、の順にすると無駄な再設定のループを避けられます。

401/403 の切り分け

AAD トークンは取れているのに Power BI API が拒否する場合、疑う場所には順番があります。

確認すること 典型的な症状
1 テナント設定「サービスプリンシパルによる API の使用を許可」 すべての API が 401
2 MI がセキュリティグループに入っているか 設定は有効なのに 401 が続く
3 グループがワークスペースに Member 以上で入っているか レポート取得は通るが GenerateToken が失敗

上から順に潰すのが最短です。逆順に疑うと、正しく設定できているワークスペース権限を何度も付け直すことになります。

tokenExpired というイベントは存在しない

トークン期限切れをハンドリングしようとして report.on("tokenExpired", ...) と書きたくなりますが、powerbi-client にそのイベントはありません。登録しても無効イベントとして黙って捨てられます。正しくは error イベントの errorCode で判定します。

JAVASCRIPT
// 期限切れは error イベントの errorCode で判定する
report.on("error", (event) => {
  if (event?.detail?.errorCode === models.CommonErrorCodes.TokenExpired) {
    refreshEmbedToken(); // 再発行して report.setAccessToken() を呼ぶ
    return;
  }
  showError(event?.detail?.message);
});

なお期限そのものは思ったより長く、筆者の環境 (v2 + Managed Identity) の実測では約 24 時間ありました。文献によって「1 時間程度」とする記述も見かけますが、自前の思い込みで実装せず、GenerateToken 応答の expiration を信頼するのが確実です。

運用の勘どころ

期限切れからの復帰処理まで作り込む。 上記のとおり実測 24 時間なので、再発行が動くのは「タブを開きっぱなしにした翌日」のような稀ケースですが、その保険が壊れていると気づきにくいものです。再発行に成功したらエラー表示の状態を必ず解除することを忘れずに。エラー表示が残ったままだと、トークンは新しくなったのに画面は赤いまま、という状態になります。

CSP を使っているなら frame-src に注意。 埋め込み iframe のために https://app.powerbi.com を許可するのは自明ですが、blob: も必要です。レポートのエクスポート/ダウンロード機能が blob: の iframe を動的に生成するため、塞ぐとその機能だけが無言で死にます。

レイアウトは FitToWidth が扱いやすい。 既定の FitToPage は埋め込み枠が横長になるほど上下に余白が出ます。また、レポート本体 (16:9) の高さだけ確保するとページナビゲーション (約 40px) が溢れて iframe 内にスクロールバーが出るので、枠の高さはナビ分を足して確保します。

pause を使ってよいのは開発・検証容量まで。 本番容量を止めるとその瞬間から顧客にレポートが見えなくなるため、pause によるコスト削減は本番では成立しません。本番は月額固定費として予算化し、調整は SKU サイズと予約割引で行います。

制約事項 (2026 年 8 月時点)

  • 容量なしで発行される試用トークンは開発専用です。「Free trial version」バナーが常時表示され、発行数にも上限があります。本番リリース前に容量の紐付けが必須です
  • A SKU は現役ですが、一度 Fabric への統合 (廃止) が発表され撤回された経緯があります。新規に始めるなら F SKU との比較を含め、最新のアナウンスを確認してください
  • かつて定番だった Microsoft 365 開発者プログラムの無料サンドボックスは、現在は Visual Studio サブスクライバー等に限定されています。無料で試すなら Fabric の 60 日試用版 + Power BI 個人試用版が現実的な経路です
  • MI (サービスプリンシパル) では Power BI サービスの UI にサインインできません。レポートの発行・確認は Pro ユーザーで行います
  • 仕様は更新されていくため、最新の公式ドキュメントを確認してください

まとめ

App owns data の埋め込みは、登場する 2 種類のトークンの役割分担 — AAD トークンはサーバーから出さない、フロントへ渡すのは embed トークンだけ — さえ押さえれば、見た目ほど複雑ではありません。さらに Managed Identity を使えば、サーバー側にすらシークレットを置かない構成にできます。守るべき秘密が最初から存在しないことは、どんな保管・ローテーション運用よりも強力です。そして埋め込みページと embed トークンの発行 API は、必ず自社の認証機構の内側に置く — これがこの構成の大前提です。

実装の順番としては、権限 (MI → セキュリティグループ → テナント設定 → ワークスペース Member) を先に固めて curl で疎通を確認し、そのあとでコードを書くのがおすすめです。権限とコードの問題を同時に抱えると切り分けが一気に難しくなります。まずは開発容量なし + 試用トークンで、最初の 1 レポートを表示するところから始めてみてください。

このレポート埋め込みの先には、もう一段濃い領域があります。REST API でセマンティックモデルから生データを取り出す話は、続編「Power BI executeQueries で生データを取り出す — API の制約とページング設計」として公開しています。

参考リンク

更新履歴

    • Power BI Embedded の埋め込み入門記事を公開