Microsoft Graph で SharePoint Online のリストにアクセスしたときだけ 403 Forbidden になり、「PnP.PowerShell からはフルコントロールで見えているのになぜ?」と悩むケースは非常に多いです。この記事では、よくある勘違いポイントを整理しつつ、実務で役立つ切り分け手順と具体的な対処策を詳しく解説します。
Microsoft Graph で SharePoint リスト取得が 403 Forbidden になる典型シナリオ
まずは、よくある前提条件を整理しておきます。あなたの環境も、かなり近い構成になっているはずです。
| 項目 | 内容 |
|---|---|
| 用途 | SharePoint Online 上のドキュメント管理アプリ(独自 Web / SPA / バックエンド) |
| 認証基盤 | Microsoft Entra ID(旧 Azure AD)のアプリ登録 |
| 付与した権限 | 委任権限:Sites.ReadWrite.All(Microsoft Graph) |
| 認可フロー | 認可コードフロー(Authorization Code Flow)でアクセストークン取得 |
| 呼び出し API | GET /sites/{site-id}/lists/{list-id}/items?$expand=fields |
| 発生している事象 | Graph 呼び出しのみ 403 Forbidden(Access denied) 一方、PnP.PowerShell でサイトに接続すると、ユーザーはサイトに対してフルコントロールを持っているように見える |
この状況の多くは、
- 権限の種類
- トークンの中身
- テナント側の条件付きアクセス
- 対象リソース(サイト/リスト)の指定
のどこかが噛み合っていないことが原因です。逆に言えば、この4つを順番に潰していくことで、かなりの確率で解決できます。
なぜ PnP.PowerShell は通るのに Microsoft Graph が 403 になるのか
まず押さえておきたいのは、「PnP で見えている=Graph でも必ず見える」ではないということです。
- PnP.PowerShell は主に SharePoint REST API(/_api) に対してアクセスする
- Graph 呼び出しは Microsoft Graph(https://graph.microsoft.com) に対してアクセスする
この2つは同じ SharePoint データを扱っていても、
- 適用される 条件付きアクセス(CA)ポリシー が違う
- 必要になる Graph スコープ/アプリロール が違う
- トークンの aud(受信者) がそもそも別
といった違いがあります。
そのため、たとえば次のようなことが起こり得ます。
- ブラウザや PnP.PowerShell から SharePoint サイトにアクセスするのは許可されている
- しかし Microsoft Graph へのアクセスには「準拠デバイス必須」などの CA が追加で掛かっている
- 結果として、SharePoint REST は成功するのに Graph は 403 という差が出る
ここから先は、よくある原因を 1 つずつ深掘りしながら、どう切り分けていくかを解説します。
よくある原因パターン一覧
まずは全体像を俯瞰できるように、原因パターンを表にまとめます。
| 原因パターン | 概要 | 優先度 |
|---|---|---|
| トークンのスコープ不足・管理者同意不足 | トークンの scp に Sites.ReadWrite.All が入っていない/管理者同意が付与されていない | ★★★★★(最優先で確認) |
| 条件付きアクセス・デバイス準拠ポリシー | Graph だけ CA ポリシーが強く、トークンがブロックされている | ★★★★☆ |
| リストの固有権限 | サイトではフルコントロールだが、対象リストだけ権限継承が切られている | ★★★☆☆ |
| site-id / list-id の取り違え | 実は別サイト/別リストを指しており、権限不足扱いになっている | ★★★☆☆ |
| 委任権限 vs アプリケーション権限の誤解 | バックエンドからアプリ単独で呼んでいるのに、委任権限しか設定していない(または逆) | ★★★☆☆ |
トークンの中身を確認する(最優先ステップ)
403 Forbidden が出たら、まずやるべきは 実際に使っているアクセストークンをデコードして中身を見ること です。ここを見ずに手探りで設定を変え続けると、原因にたどり着くまで非常に時間が掛かります。
確認すべき主なクレーム
- aud(Audience)
トークンの宛先。Microsoft Graph 用なら GUID で00000003-0000-0000-c000-000000000000など Graph を示す値、またはhttps://graph.microsoft.comになっていること。 - scp(Scope)
委任権限で発行されたトークンに付く権限の一覧。
例:Sites.ReadWrite.All User.Read openid profile - roles
アプリケーション権限で発行されたトークンに入るアプリロールの一覧。委任トークンの場合は通常空、または存在しません。 - exp / nbf
有効期限・有効開始時刻。単にトークンが古くて期限切れになっている場合もあります。
委任権限で Graph を呼んでいる場合、最低限次のような状態になっているかチェックします。
audが Microsoft Graph を指しているscpにSites.ReadWrite.All(または少なくともSites.Read.All)が含まれている
逆に、アプリケーション権限で動かす設計にしているなら、
scpは空(または存在しない)rolesにSites.ReadWrite.AllやSites.Selectedなどのアプリロールが入っている
この時点で 期待する権限がまったく入っていない 場合、アプリ登録の設定か、スコープ要求の仕方、または管理者同意が怪しいと絞り込めます。
WWW-Authenticate ヘッダーの claims チャレンジ
403 のレスポンスヘッダーの中に WWW-Authenticate が含まれていたら、その中の claims= を必ず確認しましょう。条件付きアクセスが原因の場合、ここに
- 多要素認証を要求
- 準拠デバイスが必要
- 特定のサインインリスクレベル以上をブロック
といった追加条件が JSON 形式で埋め込まれていることがあります。これがある場合、単に権限を増やすだけでは解決せず、「アプリ側が claims を含めてリダイレクトし直す」などの対応が必要になります。
管理者同意(Admin Consent)が付与されているか確認する
委任権限の Sites.ReadWrite.All は管理者同意必須 のスコープです。ユーザーが自分でサインインしただけでは、トークンにこのスコープが載らないケースがよくあります。
管理者同意の確認と付与手順
- Microsoft Entra 管理センター(旧 Azure ポータル)で「アプリ登録」を開く
- 該当アプリを開き、「API のアクセス許可」を表示
- Microsoft Graph の項目に
Sites.ReadWrite.All(委任)があることを確認 - 画面上部の「管理者の同意を付与」ボタンをクリック
管理者同意を付与した後は、ユーザーに一度サインアウトしてもらい、改めてサインインしてトークンを取り直す ことも重要です。古いトークンを握ったままだと、いつまでも 403 が続きます。
よくある落とし穴
- 開発テナントでは自分がグローバル管理者なので気付かないが、本番テナントでは開発者が管理者同意を付与できずに詰まる
- リダイレクト URI を追加したタイミングなどで再同意が必要になっているが、誰も気付いていない
委任権限とアプリケーション権限の違いを整理する
403 トラブルのかなりの割合は、「委任でやるつもりだったのか、アプリ単独でやるつもりだったのか」がチーム内で曖昧なまま進んでいることに起因します。
| 項目 | 委任権限(Delegated) | アプリケーション権限(Application) |
|---|---|---|
| 実行主体 | サインインしているユーザー + アプリ | アプリのみ(バックエンド・バッチ) |
| トークンのクレーム | scp にスコープが入る | roles にアプリロールが入る |
| 動作する条件 | ユーザーが対象サイト/リストを読む権限を持っていることが前提 | SharePoint 管理側でアプリにサイト権限を明示的に割り当てる必要がある |
| 典型的な用途 | ユーザーがブラウザから操作する業務アプリ | 夜間バッチ、同期処理、Webhook など |
「バックエンドからサーバーサイドで自動実行したいのに、委任権限しか付いていない」状態で呼ぶと、期待とまったく違うトークンが発行され、403 に繋がります。設計の前提を一度整理して、必要に応じて Sites.Selected(アプリケーション) などに切り替えることを検討しましょう。
サイト/リスト固有の権限を確認する
「PnP.PowerShell でサイトに接続するとフルコントロールなのに、Graph からは 403」という場合でも、よく調べると 対象リストだけ権限継承が切られている ケースは少なくありません。
PnP.PowerShell で確認する例
# サイトに接続
Connect-PnPOnline -Url https://<tenant>.sharepoint.com/sites/<siteName> -UseWebLogin
# リストの情報を取得
Get-PnPList -Identity "<ListName>" | Select Title, HasUniqueRoleAssignments
# リストの権限割り当てを確認
Get-PnPRoleAssignment -List "<ListName>"
HasUniqueRoleAssignmentsがTrueの場合、そのリストはサイトからの権限継承を停止している- 委任トークンの主体になっているユーザー(
upnクレームなど)が、ここに含まれているか確認する
もし対象ユーザーに権限が無い場合、Graph からのアクセスは 403 になります。PnP の実行ユーザーと、アプリでサインインしているユーザーが 別アカウント なこともあるので、ここは丁寧に突き合わせてください。
site-id / list-id を Graph で取り直して確認する
URL の ID を一文字でも間違えると、Graph は「権限がない」「リソースが存在しない」等のエラーを返します。見た目は 403 でも、実際は ID 取り違えが原因ということも多いです。
サイト ID の取得
GET https://graph.microsoft.com/v1.0/sites/{hostname}:/sites/{site-path}?$select=id,name,webUrl
{hostname}例:contoso.sharepoint.com{site-path}例:sites/DocManagement
リスト ID の取得
GET https://graph.microsoft.com/v1.0/sites/{site-id}/lists?$select=id,name,displayName
ここで返ってきた id をそのまま、リスト項目取得の API に差し込むようにします。
リスト項目取得の再試行
GET https://graph.microsoft.com/v1.0/sites/{site-id}/lists/{list-id}/items
?$expand=fields($select=Id,Title)
この 3 ステップで ID を「Graph ベースで」取り直すことで、手入力ミスやコピペミスを潰すことができます。
条件付きアクセス(CA)・デバイス準拠ポリシーの影響を疑う
トークンの中身も ID も問題ないのに 403 が続く場合、次に疑うべきは 条件付きアクセス です。特に企業テナントでは、SharePoint と Graph に対して別々のポリシーが掛かっていることがよくあります。
よくある設定例
- SharePoint Online:社外 IP からもブラウザアクセス可(ただし MFA 必須)
- Microsoft Graph:社内 IP または準拠デバイスからのみ許可
この場合、PnP.PowerShell は「ブラウザ相当」と見なされて SharePoint 側のルールに従いますが、Graph 呼び出しはより厳格なポリシーで評価され、結果として 403 になります。
CA 由来かどうかを見分けるポイント
- サインインログ を確認し、「条件付きアクセス」の欄でブロックされていないかを見る
- レスポンスヘッダーの
WWW-Authenticateにclaimsチャレンジが含まれていないか確認する - 同じユーザー・同じネットワークから Graph Explorer で同 API を呼んでみて成功するかどうかを試す
Graph Explorer で通るのに自作アプリからは 403 の場合、アプリが CA の評価条件(クライアントアプリ種別、フロー、claims 対応など)を満たしていない可能性が高いです。
Graph Explorer / Graph CLI での再現確認
アプリ固有の問題なのか、テナント/ポリシー全体の問題なのかを見分けるために、Graph Explorer(ブラウザ)や Microsoft Graph CLI で同じ API を叩いてみるのは非常に有効です。
Graph Explorer での確認例
- 同じユーザーアカウントで Graph Explorer にサインイン
- リクエスト欄に以下のように入力
GET /v1.0/sites/{site-id}/lists/{list-id}/items?$expand=fields - 実行結果が 200 で返るかどうかを確認
- ここで成功する → アプリ側の設定・実装の問題 に絞られる
- ここでも 403 → ユーザー権限か条件付きアクセス/テナント設定の問題 に絞られる
アプリケーション権限 + Sites.Selected で運用する場合
バックエンドやバッチ処理で「人がログインしていなくても SharePoint リストを見に行きたい」場合は、委任権限ではなく アプリケーション権限 を使う方が自然です。その際、最近のベストプラクティスは Sites.Selected を使って、アプリごとにアクセスできるサイトを絞る方法です。
大まかな設定手順
- アプリ登録で Microsoft Graph に アプリケーション権限 Sites.Selected を追加
- 管理者同意を付与
- SharePoint 管理者として、対象サイトにアプリ権限を割り当て
SharePoint 側での権限付与には、PnP.PowerShell の Grant-PnPAzureADAppSitePermission コマンドがよく使われます。
Grant-PnPAzureADAppSitePermission `
-AppId <appId> `
-DisplayName "<display-name>" `
-Site https://<tenant>.sharepoint.com/sites/<siteName> `
-Permissions Write
この手順を踏むと、トークンの roles に Sites.Selected が入り、対象サイトに対してのみ Graph からアクセスできるようになります。
デバッグのときに必ず取っておきたい情報
403 Forbidden の原因を後から追いかけるためにも、次の情報はログなどに必ず残しておくと便利です。
- HTTP ステータスコード(例:403)
- レスポンスボディのエラーメッセージ(
error.code,error.message) - 重要ヘッダー:
x-ms-*系、WWW-Authenticate - 使用したトークンの内容(ヘッダーと署名を除いた ペイロード部分だけ をデコードして記録)
- 呼び出した URL(クエリストリングも含めて)
cURL での再現テスト
アプリコードをいじる前に、まずは純粋な HTTP リクエストで再現させてみると、余計な要因を排除できます。
curl -i -H "Authorization: Bearer <access_token>" \
"https://graph.microsoft.com/v1.0/sites/<site-id>/lists/<list-id>/items?$expand=fields"
ここで 403 が返るなら、コードではなく構成(権限・CA・ID)側に問題があると判断できます。
403 Forbidden を解消するための実践的チェックリスト
最後に、実務での「つぶしこみ順」を表にまとめます。上から順に確認していくことで、無駄なく原因にたどり着けるはずです。
| ステップ | 確認内容 | 具体的なアクション |
|---|---|---|
| 1. トークン内容の検査 | aud / scp / roles / exp を確認し、期待する権限が入っているか | JWT をデコードしてログに貼り付け、Sites.ReadWrite.All などが入っているか目視で確認 |
| 2. 管理者同意の再付与 | 委任権限の Sites.ReadWrite.All に Admin Consent が付いているか | Entra ポータルの「API のアクセス許可」で「管理者の同意を付与」を実行後、ユーザーに再サインインさせる |
| 3. Graph Explorer での動作確認 | 同じユーザー・同じ API で 200 が返るか | Graph Explorer にサインインし、同一エンドポイントを叩く |
| 4. リスト固有権限の確認 | 対象リストが固有権限を持っていないか、ユーザーに権限があるか | PnP で HasUniqueRoleAssignments や Get-PnPRoleAssignment を確認 |
| 5. site-id / list-id の再取得 | ID の取り違えや手入力ミスがないか | Graph からサイト ID → リスト ID → 項目取得の順で再取得し、URL を修正 |
| 6. 条件付きアクセスの評価 | CA ポリシーで Graph アクセスがブロックされていないか | サインインログの「条件付きアクセス」の項目と WWW-Authenticate の claims チャレンジを確認 |
| 7. 設計の見直し(任意) | バックエンド処理ならアプリケーション権限の方が適切かどうか | Sites.Selected + サイトへのアプリ権限割り当てを検討 |
まとめ:403 Forbidden を怖がらず、情報から冷静に切り分ける
Microsoft Graph で SharePoint リストを取得しようとして 403 Forbidden に遭遇すると、「何をどう直せばよいのか分からない」状態になりがちです。しかし、
- トークンの内容(
aud / scp / roles / exp) - 管理者同意の有無
- サイト/リストの実際の権限
- site-id / list-id の整合性
- 条件付きアクセスの評価結果
といった情報を 1 つずつ確認していけば、多くの場合は原因を特定できます。
特に、「PnP では見えるのに Graph では見えない」ケースは、API の違いよりもテナント側ポリシーや権限設計の違いが原因になっていることが多い です。PnP の実行ユーザーとアプリが利用しているユーザー/アプリケーション権限を丁寧に見比べ、今回紹介したチェックリストを順番に辿っていけば、403 Forbidden は決して怖い相手ではありません。
もしそれでも解決しない場合は、使用しているトークンペイロード(aud と scp だけ抜き出したもの)や、サインインログの「条件付きアクセス」の評価結果を整理し、チーム内やコミュニティで共有すると、より具体的なアドバイスを得やすくなります。

コメント