Microsoft Graph(Universal Print)を.NET Core Web APIからApp-only(アプリ権限)で呼び出し、/print/printers/{id}/jobs にPOSTすると403や「the token does not have one or more required security scopes」で詰まることがあります。原因と公式仕様、そしてDelegated+OBOで実現する実装パターンをまとめます。
現象:App-only(アプリ権限)で Print Job を作成しようとすると403になる
.NET Core Web API(バックグラウンド実行)から Microsoft Graph Universal Print を呼び出し、次のように印刷ジョブ(printJob)を作成しようとしたところ、403 Forbidden で失敗するケースです。
- 呼び出し先:
POST /print/printers/{printerId}/jobs - 認証:client credentials(クライアント資格情報フロー)で取得したアクセストークン(App-only)
- Entra ID(Azure AD)側:Application permissions(例:PrintJob.ReadWrite.All、Printer.ReadWrite.All など)を付与し、Admin consent 済み
- jwt.ms で見ると
rolesに権限が入っているように見える - それでも Graph から「the token does not have one or more required security scopes」
典型的なエラーレスポンス(例)は次のような形です。
{
"error": {
"code": "Forbidden",
"message": "The token does not have one or more required security scopes."
}
}
結論:printJob作成(POST /jobs)は Application permissions 非対応
ここが最大の落とし穴です。プリンターに対する printJob 作成(POST /print/printers/{printerId}/jobs)は、Delegated permissions(委任権限)前提で、Application permissions(アプリ権限)はサポートされていません。 公式ドキュメントの「Permissions」表に、Application が Not supported と明記されています。
つまり、いくらアプリ登録に Application permissions を盛って Admin consent しても、エンドポイント側が App-only トークンでの実行を受け付けないため、期待通りには動きません。
| 操作 | エンドポイント | Delegated | Application(App-only) |
|---|---|---|---|
| 印刷ジョブ作成(プリンター) | POST /print/printers/{printerId}/jobs | 対応(例:PrintJob.Create) | 非対応 |
| 印刷ジョブ作成(プリンター共有) | POST /print/shares/{printerShareId}/jobs | 対応 | 非対応 |
また、印刷の最終実行に相当する「start(ジョブ開始)」も、Application permissions は非対応です。App-only で最後まで押し切る設計は成立しません。
なぜ「required security scopes」と言われるのか(rolesが入っていても)
ここで混乱が起きやすい理由は、トークンの「権限の載り方」が Delegated と App-only で違うことにあります。
Microsoft の解説では、アプリケーション権限(client credentials で得たトークン)では権限が roles に入り、委任権限(ユーザーのサインインを伴うトークン)では権限が scp に入る、と整理されています。さらに、アプリケーション権限トークンでは scp が存在しない点も明記されています。
| 観点 | Delegated(委任) | App-only(アプリ権限) |
|---|---|---|
| ユーザーのサインイン | 必要 | 不要(アプリ自身として認証) |
| トークン内の代表的な権限クレーム | scp(scope文字列) | roles(アプリロール) |
| 今回の失敗の根本原因 | — | エンドポイントが委任前提のため、App-onlyトークンでは満たせない |
今回のエラーメッセージは「scopes(scp)が足りない」と表現されがちですが、実際には「この操作は委任トークン(scp前提)でしか許可しない」という仕様に当たっている、と理解するとスッキリします。
Universal Printでも App-only が使えるAPIはある(ただし“印刷”は別)
Universal Print が全部 Delegated 専用というわけではありません。たとえば、プリンターのジョブ一覧取得は Application permissions をサポートしています(最小権限として PrintJob.ReadBasic.All など)。
一方で、「ジョブを作る」「ジョブを開始する」= 実際に印刷を発生させる操作は、原則として Delegated 前提です。少なくとも現行の Graph 公式仕様では、printJob 作成および start は App-only 非対応です。
「ユーザー同意なしで印刷したい」は現状どう扱うべきか
「完全にユーザー不在(ユーザー操作ゼロ)で印刷ジョブ作成まで行いたい」という要件は、Universal Print の標準的な“Web/Mobile からの印刷”シナリオとは噛み合いません。
Microsoft Q&A でも、アプリケーション権限だけでの印刷(ジョブ投入)はできず、ジョブ投入には委任権限が必要、アプリケーション権限は pull printing(後述)のセットアップ用途に使う、という説明がされています。
このため、現実的には次のどれかに落ち着きます。
| 方針 | できること | 向いているケース | 注意点 |
|---|---|---|---|
| Delegated+OBO(推奨) | ユーザーの権限で印刷ジョブ作成~開始 | 社内業務Web、申請画面、帳票印刷 | ユーザーの初回サインインは必要 |
| 運用ユーザー(サービスアカウント)での委任 | “特定ユーザー”として印刷 | バックオフィスの自動帳票出力 | 結局ユーザー主体。アカウント管理・監査が重要 |
| pull printing(拡張シナリオ) | 印刷イベントをトリガーにアプリが介入 | セキュアプリント、バッジ認証、リダイレクト | セットアップはApp権限でも、ジョブ投入自体は別 |
| Universal Print以外の印刷経路 | ローカル印刷、別基盤で出力 | “どうしてもユーザー不在”が必須 | 要件とセキュリティ方針の再整理が必要 |
印刷の正しい手順:Create Job → Upload → Start
Universal Print で「印刷」を完了させるには、基本的に次の流れになります。Microsoft Learn の解説でも、print job を作成し、ドキュメントをアップロードし、最後に start するという順番が示されています。
- printJob を作成(作成時に printDocument も紐づく)
- printDocument に対して uploadSession を作成
- uploadUrl に PUT でバイト列をアップロード(分割アップロード可)
- printJob を start(プリンター/共有へ送る)
アップロード時の実務的なポイントとして、分割アップロードで1回あたりの最大サイズ制限(10MB未満)や、分割サイズの推奨(例:200KB単位、Graph SDK使用時は320KB単位が必要)といった注意も明記されています。大きめのPDFを扱う帳票印刷では、ここで詰まりがちです。
補足:uploadSessionは“App-onlyでも動く”ように見えるが条件付き
printDocument の createUploadSession は Application permissions(例:PrintJob.ReadWrite.All)を表上はサポートします。ただし、App-onlyで成功するのは、アプリが作成したトリガーにより printTask が processing 状態になっているジョブに対してのみ、という制約が付いています。一般的な「Webアプリから即印刷」の文脈では、ここをApp-onlyで抜けても start がApp-only非対応なので、やはり全体は委任前提になります。
推奨アーキテクチャ:フロントでDelegated取得 → Web APIでOBO → Graph呼び出し
「Web APIだけでユーザー同意なし」は難しい一方、“ユーザーの初回サインイン(同意)”を最小化して、以降はスムーズに運用することは可能です。その定番が OBO(On-Behalf-Of)です。
流れを一言で書くと、次のようになります。
ユーザー(ブラウザ)
↓(MSALでサインイン)
フロントエンド(SPA/Angular/Reactなど)
↓(APIトークンを付けて呼ぶ)
.NET Web API(ユーザーとして認可済み)
↓(OBOでGraph用トークンを取得)
Microsoft Graph(Universal Print)
Microsoft Graphの委任とアプリケーション権限の基本整理として、委任は「サインインユーザーの代わりに」Graphを呼ぶ形で、アプリ権限は「ユーザーなしでアプリ自身として」呼ぶ形です。今回のprintJob作成は前者が必要です。
実装の勘所:Entra ID(Azure AD)側の設定で迷わないために
アプリ登録の分け方
- フロントエンド(SPA)用:ユーザーサインインと、Web API呼び出し用のトークン取得
- Web API用:受け取ったトークンを検証し、必要に応じて OBO で Graph を呼ぶ
Graph権限は「Delegated」で揃える
printJob作成に必要な最小権限は、原則として Delegated の PrintJob.Create が起点になります(必要に応じて PrintJob.ReadWrite* 系を検討)。アプリ権限(Application permissions)側に PrintJob.ReadWrite.All 等を付けても、printJob作成エンドポイント自体がApp-only非対応なら効果はありません。
「/printers」と「/shares」を混ぜない
Universal Print では、ユーザーが通常印刷する先は printerShare であることが多く、ジョブ開始(start)のHTTP requestも /print/shares/{printerShareId}/jobs/{printJobId}/start になっています。設計時点で「ユーザーが使うプリンター共有」を軸にID管理したほうが、後でハマりにくいです。
実装例:.NET Web API で OBO して Universal Print にジョブ投入する
ここでは「フロントでユーザーがログイン」→「Web APIでOBO」→「Graphで印刷」を想定して、実装上の要点だけを示します(実際のエラーハンドリングや監査ログは運用要件に合わせて追加してください)。
Web API側:Graphを呼ぶためのユーザートークンを取得(OBO)
OBO自体は Microsoft.Identity.Web を使う構成が取り回しやすいです。ポイントは、Web APIが受け取ったユーザートークンを元に、Graph用の委任トークンを取得して呼ぶことです。
// 例:必要となるGraphスコープ(Delegated)
// PrintJob.Create は「ジョブ作成~アップロード~開始」の土台になりやすい
string[] graphScopes = new[]
{
"PrintJob.Create",
"Printer.Read.All"
};
そして Graph SDK で printJob 作成自体は、公式のコード例でも次のような呼び出し形になります(重要なのは、この呼び出しが“委任トークン”で実行されることです)。
var requestBody = new PrintJob
{
Configuration = new PrintJobConfiguration
{
OdataType = "microsoft.graph.printJobConfiguration",
Copies = 1,
Orientation = PrintOrientation.Portrait,
DuplexMode = PrintDuplexMode.OneSided
}
};
var result = await graphClient.Print.Printers["{printer-id}"].Jobs.PostAsync(requestBody);
印刷までのREST手順(要点だけ)
SDKで隠れてしまう部分もあるため、RESTの形でも全体像を押さえておくとデバッグが速くなります。
- printJob作成
POST https://graph.microsoft.com/v1.0/print/shares/{printerShareId}/jobs Content-Type: application/json { "configuration": { "@odata.type": "microsoft.graph.printJobConfiguration", "copies": 1 } }レスポンスのdocuments[0].id(printDocument ID)を控える - uploadSession作成(printDocument)
POST https://graph.microsoft.com/v1.0/print/shares/{printerShareId}/jobs/{jobId}/documents/{documentId}/createUploadSession Content-Type: application/json { "properties": { "documentName": "sample.pdf", "contentType": "application/pdf", "size": 123456 } } - uploadUrlへPUTでアップロード(分割可)
PUT {uploadUrl} Content-Range: bytes 0-999999/1234567 Content-Length: 1000000 (binary)分割アップロードの制約・推奨(10MB未満、分割サイズの目安など)を前提に実装する - start(ジョブ開始)
POST https://graph.microsoft.com/v1.0/print/shares/{printerShareId}/jobs/{jobId}/startstart は Application permissions 非対応なので、ここも委任トークンで呼ぶ必要があります。
「一度同意したらずっと動かしたい」運用で押さえるべきポイント
印刷ジョブ作成が Delegated 前提である以上、運用の焦点は「ユーザーに毎回同意やログインを求めない」設計に移ります。
- 同意は初回だけに寄せる:ユーザーが最初にサインインして同意すると、その後は同意済みの範囲でトークンが発行され、通常は毎回プロンプトされません(ポリシー変更等を除く)
- Web APIはトークンの“再発行役”になる:フロントから来たアクセストークンでWeb APIがOBOし、Graph用トークンを取得して呼ぶ
- トークンは短命前提で設計する:長期運用は、MSAL/Microsoft.Identity.Webのキャッシュ(必要なら分散キャッシュ)を活用して“途切れない体験”に寄せる
Microsoft の解説でも、委任トークンはユーザーのサインインを前提とし、OBOも委任トークンを得るためのフローとして挙げられています。
よくある落とし穴チェックリスト
| 症状 | ありがちな原因 | 確認ポイント |
|---|---|---|
| rolesに権限があるのに「required security scopes」 | App-onlyトークンでDelegated専用エンドポイントを叩いている | 対象APIのPermissions表でApplicationがNot supportedになっていないか |
| ジョブは作れたが印刷されない | アップロード未完了/start未実行 | create→upload→start の順に実装できているか |
| uploadが途中で失敗する | 分割サイズやContent-Rangeの不整合 | 1回あたり10MB未満、推奨サイズ、並列数などの前提を満たす |
| createUploadSessionだけApp-onlyでやりたくなる | 「一部APIがApplication対応」に引っ張られる | pull printing の条件付きであることを理解し、全体設計で判断 |
| 「/printers」でやっていたのにstartが「/shares」 | printerとprinterShareの役割混同 | ユーザー起点の印刷はshare中心で設計し、ID管理を統一 |
pull printing(拡張)を検討する場合の考え方
「ユーザーがバッジをかざしてから印刷」「ジョブを別プリンターへ振り替える」「ジョブを保持して承認後に出す」など、いわゆるセキュアプリント・プルプリントの要件がある場合、Universal Print API にはトリガーや printTaskDefinition を使ってアプリが印刷ワークフローに介入する拡張シナリオがあります。
この拡張シナリオでは、セットアップ手順の中に「application permissions を使って printTaskDefinition を作成する」といった記述があり、App-onlyが活きる場面があります。ですがこれは「印刷ジョブ投入をApp-onlyで行う」話とは別軸です。要件が“無人印刷”なのか“セキュアプリント”なのかを切り分けることが重要です。
まとめ:App-onlyで詰まったら、まず“エンドポイントの対応表”を確認する
- printJob 作成(POST /jobs)は Application permissions 非対応なので、App-onlyでは作れない
- 印刷は create → upload → start の3段で、startもApplication非対応
- 「rolesがあるのにscope不足」は、Delegated専用APIにApp-onlyで突っ込んでいるサイン
- 現実的な解決は、Delegated+OBOで“ユーザー同意を最小化しつつ”Web APIからGraphを呼ぶ

コメント