Microsoft Graph API の Sites.Selected を使って「このアプリはこの SharePoint サイトだけ触れるようにしたい」と考えたのに、POST /sites/{site-id}/permissions が 403 で落ちてしまう――この記事では、その典型的な原因と、実運用で安全かつシンプルに構成するための手順をまとめます。
Microsoft Graph API で SharePoint サイトにアプリ権限(Sites.Selected)を付与できない原因
まず、よくある状況を整理します。
- 対象:SharePoint Online の特定サイト
- やりたいこと:特定のアプリにだけ、そのサイトへのアクセスを許可したい
- 実行している操作:
POST /sites/{site-id}/permissionsにより、サイトにアプリのアクセス権を付与しようとしている - 発生している問題:
- レスポンスが 403 Forbidden
- メッセージ例:「サインインユーザーに権限が不足」など
- 前提条件:
- アプリには
Sites.ReadWrite.All、Sites.Selected、User.Readを付与済み - 実行ユーザーは対象サイトのサイト管理者
- アプリには
一見、「サイト管理者だし Sites.ReadWrite.All もあるし、権限は足りているのでは?」と思いがちですが、サイトにアプリ権限を付与する操作には別の条件があります。
結論:付与操作を行う側には Sites.FullControl.All が必須
大事なポイントは次の 2 つです。
- サイトへのアプリ権限付与(
POST /sites/{site-id}/permissions)を実行する側には、Sites.FullControl.Allが必須 - 実際にアクセスさせたい「制限対象アプリ」には、
Sites.Selectedだけを付与する(広域権限を併用しない)
これを整理すると、次のようになります。
| 役割 | 用途 | 必要な権限 | 備考 |
|---|---|---|---|
| 付与操作を行うアプリ / ユーザー | サイトに「どのアプリをどの権限で許可するか」を登録する | Sites.FullControl.All(アプリケーション権限) | 委任の場合は、Graph アプリに Sites.FullControl.All を付与 & サイト コレクション管理者で実行 |
| 制限対象アプリ | 実際にサイト内のファイルやリストにアクセスする | Sites.Selected(アプリケーション権限) のみ | Sites.ReadWrite.All などの広域権限を併用するとサイト限定にならない |
今回 403 が出ていた原因は、多くの場合この「付与操作側に Sites.FullControl.All が無い」ことにあります。
実際に、Sites.FullControl.All を付けたら解消したという報告があり、再現性の高いパターンです。
Delegated(委任)と App-only での違い
POST /sites/{site-id}/permissions を実行する方法によって、必要条件が少し変わります。
| 実行方法 | 必要な条件 | 典型的な利用例 |
|---|---|---|
| Delegated(委任) | 使用しているアプリ(例:Graph Explorer)に Sites.FullControl.All(委任権限) の管理者同意 サインインユーザーが対象サイトのサイト コレクション管理者 | Graph Explorer などで手動付与する場合 |
| App-only(アプリのみ) | 管理用アプリに Sites.FullControl.All(アプリケーション権限) そのアプリのトークンで API を実行 | スクリプトやバッチで複数サイトをまとめて設定する場合 |
運用を考えると、管理用アプリを 1 つ作って App-only で実行する構成がもっともシンプルで安全です。
おすすめ構成:管理用アプリ + 制限対象アプリ
「管理用アプリ」と「制限対象アプリ」を分ける二段構えにすると、運用とセキュリティがぐっと楽になります。
構成イメージ
| アプリ種別 | 付与する権限(アプリケーション権限) | 役割 |
|---|---|---|
| 管理用アプリ | Sites.FullControl.All (必要に応じて)Directory.Read.All など | サイトに対して「どのアプリに read/write を許可するか」を設定する専用 |
| 制限対象アプリ | Sites.Selected のみ | 実際に業務処理(ファイル取得・更新など)を行うアプリ |
この構成にしておくと、誤って制限対象アプリに広域権限を付けてしまうリスクを避けられます。
最短手順(おすすめの実装フロー)
1. 管理用アプリを作成し、Sites.FullControl.All に管理者同意
- Azure AD(Entra ID)でアプリ登録を作成(名前例:
sp-sites-permission-admin)。 - API のアクセス許可で「Microsoft Graph」を選択し、アプリケーション権限から
Sites.FullControl.Allを追加。 - 管理者として 管理者の同意を付与(テナント全体に承認)。
- クライアントシークレット or 証明書を登録し、後でスクリプトから使えるようにしておく。
Graph Explorer で Delegated 実行する場合は、Graph Explorer アプリ自体に管理者が Sites.FullControl.All を同意しておき、実行ユーザーはサイト コレクション管理者である必要があります。
2. 制限対象アプリを作成し、Sites.Selected のみを付与
- 別のアプリ登録を作成(名前例:
line-of-business-app)。 - API のアクセス許可で「Microsoft Graph」→ アプリケーション権限から
Sites.Selectedを追加。 - 必ず
Sites.Read.AllやSites.ReadWrite.Allを付けないよう注意。- これらを付けると、「サイト限定」ではなく「全サイトアクセス可能」になってしまう。
- 管理者として 管理者の同意を付与。
- このアプリのアプリケーション ID(クライアント ID)を控えておく(後で JSON に記述)。
3. 管理用アプリのトークンで、対象サイトにアプリ権限を付与
管理用アプリの資格情報を使ってトークンを取得し、以下のようなリクエストを実行します。
POST https://graph.microsoft.com/v1.0/sites/{site-id}/permissions
Content-Type: application/json
Authorization: Bearer <管理用アプリのアクセストークン>
{
"roles": ["read"], // 読み取りのみ: "read" / 更新も許可: "write"
"grantedToIdentities": [{
"application": {
"id": "<制限対象アプリのアプリケーションID(クライアントID)>",
"displayName": "LOB App for Project X"
}
}]
}
重要なポイント:
rolesは"read"または"write"だけ(複数指定も可能だが、通常はいずれか)。grantedToIdentities.application.idに入れるのは、オブジェクト ID ではなくアプリケーション ID(クライアント ID)。{site-id}は SharePoint サイトの URL ではなく、Graph 上の Site ID。
Site ID は例えば次のように取得できます。
GET https://graph.microsoft.com/v1.0/sites/{hostname}:/sites/{sitePath}?$select=id
{hostname}例:contoso.sharepoint.com{sitePath}例:sites/ProjectX
4. 付与結果の確認
対象サイトに正しくアプリ権限が付与されたかどうかは、次のリクエストで確認できます。
GET https://graph.microsoft.com/v1.0/sites/{site-id}/permissions
Authorization: Bearer <管理用アプリのアクセストークン>
レスポンス内に、先ほど指定した displayName と application.id を含むエントリが追加されていれば成功です。
5. 制限対象アプリのトークンでアクセス検証
最後に、制限対象アプリの資格情報でトークンを発行し、実際にサイト配下の API を呼び出して確認します。
GET https://graph.microsoft.com/v1.0/sites/{site-id}/drive/root/children
Authorization: Bearer <制限対象アプリのアクセストークン>
- 対象サイトについては、200 OK でファイル一覧が取得できることを確認。
- 別のサイト ID で試したときには、403 Forbidden になることを確認。
この 2 つの動きが確認できれば、Sites.Selected によるサイト限定アクセスが正しく機能していると言えます。
Sites.Selected の動作を整理する
ここで一度、Sites.Selected の意味を整理します。
Sites.Selectedは、「このアプリは SharePoint にアクセスしても良いが、どのサイトにアクセスできるかはサイト側の設定で決まる」という「土台」の権限。- アプリに
Sites.Selectedを付けただけでは、どのサイトにもアクセスできません。 - アクセスを許可したいサイトごとに、
POST /sites/{site-id}/permissionsでエントリを追加して初めてアクセスできるようになる。
逆に言うと、サイト側に何もエントリを作っていない状態では、どのサイトにもアクセスできないため、/sites/{site-id}/drive などを呼ぶと 403 になります。
read と write の違い
サイトにアプリを付与する際の roles の値で、アプリがどこまで操作できるかが変わります。
| roles の値 | 意味 | 主な操作例 | 運用上のおすすめ度 |
|---|---|---|---|
["read"] | 読み取り専用 | ファイル一覧取得、ダウンロード、メタデータ参照など | まずは 最小権限としてこれを使うのが基本 |
["write"] | 読み書き・削除も可能 | ファイルのアップロード、更新、削除、フォルダー作成など | 本当に書き込みが必要な場合だけ限定して付与 |
多くのケースでは、まず ["read"] で実装 → どうしても必要な範囲に限って ["write"] にするというステップが安全です。
よくある落とし穴とチェックリスト
Sites.Selected 周りでハマりやすいポイントを、チェックリスト形式でまとめます。
| カテゴリ | 落とし穴 | 症状 | 確認・対処ポイント |
|---|---|---|---|
| 付与側権限 | 付与操作側に Sites.FullControl.All が無い | POST /sites/{site-id}/permissions が 403 Forbidden | 管理用アプリまたは Graph Explorer に Sites.FullControl.All が付いているか確認 |
| 制限対象アプリ | Sites.ReadWrite.All などの広域権限も付けてしまう | 他サイトにもアクセスできてしまい、サイト限定になっていない | 制限対象アプリは Sites.Selected のみにし、他の Sites.* 系権限を外す |
| アプリ ID 指定 | grantedToIdentities.application.id にオブジェクト ID を指定 | 付与は成功したように見えるが、期待通りに動かない / 設定がずれる | ポータルで「アプリケーション(クライアント)ID」と表示されている GUID を使う |
| ユーザー権限 | Delegated 実行時に、ユーザーがサイト コレクション管理者ではない | 403 Forbidden、「サインインユーザーに権限が不足」 | サイトの「サイト コレクション管理者」に該当ユーザーを追加する |
| Site ID 指定 | {site-id} にサイト URL を入れてしまう | 404 Not Found / 400 Bad Request | GET /sites/{hostname}:/sites/{sitePath}?$select=id で取得した id を使う |
| 構成方針 | 管理用アプリと制限対象アプリを 1 つにまとめてしまう | アプリが過剰な権限(FullControl)を持ったまま運用される | 権限付与専用の管理用アプリと、実業務用の制限対象アプリを分ける |
実装イメージ:App-only でのサンプルフロー
実際のコードでは言語ごとに異なりますが、App-only 認証の一般的なフローは次のようになります。
1. 管理用アプリでアクセストークンを取得
(疑似コード)
// 管理用アプリのクライアントID / シークレット / テナントID
var clientId = "<admin-app-client-id>";
var clientSecret = "<admin-app-secret>";
var tenantId = "<tenant-id>";
// トークンエンドポイント
var tokenUrl = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token";
// scope は Microsoft Graph の .default
var body = new {
client_id = clientId,
client_secret = clientSecret,
scope = "https://graph.microsoft.com/.default",
grant_type = "client_credentials"
};
// tokenUrl に POST して access_token を取得
2. アクセストークンを使って /permissions を呼び出し
POST https://graph.microsoft.com/v1.0/sites/{site-id}/permissions
Authorization: Bearer <access_token>
Content-Type: application/json
{
"roles": ["read"],
"grantedToIdentities": [{
"application": {
"id": "<restricted-app-client-id>",
"displayName": "LOB App"
}
}]
}
3. 制限対象アプリ側も同様にアクセストークンを取得して動作確認
制限対象アプリでも同じように client_credentials フローでトークンを取得し、そのトークンでサイト API を呼び出します。
このとき、付与されているのが Sites.Selected だけであれば、付与したサイト以外にはアクセスできない状態になります。
トラブルシューティングの具体例
実際にありがちな「ハマり方」と、そのときに確認すべき順番を具体的に挙げます。
ケース1:どうしても 403 Forbidden が消えない
- POST を実行している「主体」を特定
- Graph Explorer か、管理用アプリか、別のアプリか
- その主体に対して、Azure ポータルで
Sites.FullControl.Allが付与されているか確認 - 委任の場合は、サインインユーザーがサイト コレクション管理者かどうかを SharePoint 管理センターで確認
- 一度
GET /sites/{site-id}/permissionsを呼び、そもそも{site-id}が正しいかどうかを確かめる
ケース2:Sites.Selected にしたはずなのに、他サイトにもアクセスできてしまう
- 制限対象アプリの API アクセス許可を再確認
- Sites.Read.All / Sites.ReadWrite.All が付いていないかをチェック
- もし付いていた場合は削除し、Sites.Selected のみにした上で、管理者同意を再度実行
- アプリのトークンを取り直して、再度 API を呼び出す(古いトークンがキャッシュされていないか注意)
ケース3:/permissions のレスポンスにアプリのエントリが見つからない
POST /sites/{site-id}/permissionsのレスポンスが 201 など成功になっているか確認- JSON で指定した
grantedToIdentities.application.idに、アプリケーション ID(クライアント ID)を入れているか確認- オブジェクト ID を入れていると、意図しない動作になる可能性があります。
- 数秒待ってから
GET /sites/{site-id}/permissionsを再実行し、反映されているかを確認
運用のベストプラクティス
最後に、Sites.Selected を本番環境で運用する際のおすすめポイントをまとめます。
1. 権限は「付与する主体」と「利用する主体」を分離
- 権限付与専用の「管理用アプリ」は Sites.FullControl.All を持つが、実業務には使わない。
- 実業務用の「制限対象アプリ」は Sites.Selected のみを持ち、アクセスを許可されたサイトだけを操作。
- この分離により、万が一制限対象アプリの資格情報が漏えいしても、全サイトが危険にさらされるリスクを抑えられます。
2. まずは read で検証し、必要最小限で write を付与
- 最初から
["write"]で運用すると、誤削除や誤上書きのリスクが高まります。 - 要件を満たすかどうかを
["read"]で確認し、それでも不足する場合だけ["write"]を検討。 - アプリごとに「read 専用」「write もできる」などの役割を分けるのも有効です。
3. Sites.Selected のポリシーをドキュメント化
- どのアプリに、どのサイトへの read / write を許可したかを一覧化しておく。
- 新しいサイトや新しいアプリが追加されたときの「付与申請の流れ」も明文化。
- 監査ログやアクセスログとあわせて管理すると、トラブル時の原因特定が容易になります。
4. 検証環境で「403 を再現できる状態」を保持
- あえて
Sites.FullControl.Allを外したアプリやユーザーを用意し、「403 になるパターン」を意図的に再現できる環境を作っておくと、教育・検証に役立ちます。 - 新メンバーや別チームに仕組みを説明する際、実際に 403 → 権限追加 → 成功の流れを見せると理解が早まります。
まとめ:403 Forbidden を解消するためのポイント
Microsoft Graph API で SharePoint サイトにアプリ権限(Sites.Selected)を付与しようとして 403 Forbidden になる場合、次のポイントを確認してください。
- 付与操作を行う側に
Sites.FullControl.Allが付いているか?- 管理用アプリ(App-only)に
Sites.FullControl.Allを付与し、そのトークンでPOST /sites/{site-id}/permissionsを実行する。 - Delegated の場合は、アプリに
Sites.FullControl.All(委任) + ユーザーはサイト コレクション管理者であること。
- 管理用アプリ(App-only)に
- 制限対象アプリには
Sites.Selectedだけを付けているか?Sites.ReadWrite.Allなどの広域権限が付いていると、サイト限定の制御が効かなくなります。
grantedToIdentities.application.idはアプリケーション ID(クライアント ID)になっているか?{site-id}は正しい Site ID か?
これらを押さえておけば、Sites.Selected を使った「特定サイトだけにアクセスできるアプリ」の設計・運用が格段にやりやすくなります。
本記事を参考に、自分のテナントでも一度「管理用アプリ + 制限対象アプリ」の構成を試してみてください。

コメント