Microsoft says “Initially, Sites.Selected existed to restrict an application’s access to a single site collection. Now, lists, list items, folders, and files are also supported, and all Selected scopes now support delegated and application modes.”. This article deep-dives into providing and using SelectedOperations.Selected granular permissions to SharePoint.
SelectedOperations is exactly what Microsoft promised a few years ago. And this is great, as we really need granular access to SharePoint sites. I’ve been supporting enterprise SharePoint for more than 10 years now, and I know that it was always a concern when application require access to a list/library or a folder or even document, but admins have to provide access to entire site.
Especially, I believe, this feature becomes more in demand because of Copilot for Microsoft 365. As for now – it’s mostly developers and data analytics who needs unattended application access to SharePoint, but what if regular users powered with m365 Copilot license start creating autonomous agents?
So below is my lab setup, PowerShell scripts and guides with screenshots on how to provide granular (not to entire site but to library/list or folder, or even just one document or list item) permissions to SharePoint and how to use provided permissions.
Admin App
First, we need an Admin App – an app we will use to provide permissions.
The only requirement – this app should have Microsoft.Graph Sites.FullControl.All API permissions consented:
Target Site and Dev Setup
For this lab/demo setup, I have created a team under Microsoft Teams (so it’s a group-based Teams-connected SharePoint site), then test list and test library:
There must be an App Registration for client application – application that will have access to Test-List-01 and Test-Lib-01 only. This app registration should have Microsoft Graph “Lists.SelectedOperations.Selected” API permissions consented:
and I will use Python to access SharePoint programmatically.
At this moment (we have a client app and secret, and “” API permissions, but did not provide for this app access to specific sites or libraries) – we should be able to authenticate to Microsoft 365, but not able to get any kind of data (we can get token, but other call to Graph API would return 403 error – Error: b'{“error”:{“code”:”accessDenied”,”message”:”Request Doesn\’t have the required Permission scopes to access a site.”,):
PowerShell script to provide selectedoperations.selected access for an app to a specific list would be as below. Here we use plain calls to MS Graph API. Full script for your refence is available at GitHub, but here is the essential part:
$apiUrl = "https://graph.microsoft.com/beta/sites/$targetSiteId/lists/$targetSiteListId/permissions"
$apiUrl
$params = @{
roles = @(
"read"
)
grantedTo = @{
application = @{
id = $clientAppClientId
}
}
}
$body = $params | ConvertTo-Json
$response = Invoke-RestMethod -Headers $Headers -Uri $apiUrl -Method Post -Body $body -ContentType "application/json"
Client Application
I use Python console app as a client application. Link to the code at the GitHub is shared below under References, but the core part of the Python code is (I do not use any Microsoft or other libraries here, just plain requests to Microsoft Graph for authentication and for data):
import requests
import json
from secrets import clientSc, clientId, tenantId, siteId, listId
# specify client id, client secret and tenant id
# clientId = ""
# clientSc = ""
# tenantId = ""
apiUri = "https://login.microsoftonline.com/" + tenantId + "/oauth2/v2.0/token"
body = {
"client_id" : clientId,
"client_secret" : clientSc,
"scope" : "https://graph.microsoft.com/.default",
"grant_type" : "client_credentials"
}
try:
response = requests.post(apiUri, data=body)
token = json.loads(response.content)["access_token"]
except:
print("Error: ", json.loads(response.content)["error_description"])
exit()
print("Got token: ", token[0:10], "...")
headers={'Authorization': 'Bearer {0}'.format(token)}
# Get specific site list
print("Geting specific site list")
# graph_url = 'https://graph.microsoft.com/v1.0/sites/' + siteId + '/lists/' + listId
graph_url = 'https://graph.microsoft.com/beta/sites/' + siteId + '/lists/' + listId
graphResponse = requests.get( graph_url, headers=headers )
print(" Response status code: ", graphResponse.status_code)
if graphResponse.status_code == 200:
list = json.loads(graphResponse.content)
print(" List display name: ", list["displayName"])
Note.
I’m not sure if it’s a bug or my incorrect setup, but I noticed that if I provide access for the app to the list – app can read site.
TBC…