Microsoft Graph Universal PrintでApp-only(アプリ権限)ではPrint Jobを作成できない理由とDelegated+OBOの実装方法

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 トークンでの実行を受け付けないため、期待通りには動きません。

操作エンドポイントDelegatedApplication(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の形でも全体像を押さえておくとデバッグが速くなります。

  1. 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)を控える
  2. 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 } }
  3. uploadUrlへPUTでアップロード(分割可) PUT {uploadUrl} Content-Range: bytes 0-999999/1234567 Content-Length: 1000000 (binary) 分割アップロードの制約・推奨(10MB未満、分割サイズの目安など)を前提に実装する
  4. start(ジョブ開始) POST https://graph.microsoft.com/v1.0/print/shares/{printerShareId}/jobs/{jobId}/start start は 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を呼ぶ

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次