Just as I was finishing up my article on support for EWS in application access policies, Microsoft announced the first iteration of a similar functionality for restricting Graph API access in SharePoint Online. While still in early stages, the feature offers just enough functionality to demonstrate the usefulness of such controls, so let’s take a quick look. If you are interested in the original blog post/announcement, you can find it here.
To start with, let’s introduce the concept. In a nutshell, we can now grant the Sites.Selected scope/permission to a given application, which in turn means any calls said application tries against the Graph API will only succeed if used against a site collection(s) we have explicitly allowed (“selected”). By default, the list of allowed sites will be empty, so stamping this scope on an application effectively restricts it from running any Graph API queries against the tenant.
Adding sites (collections) to the list is currently only possible via the Graph API and the recently introduced sites/{siteId}/permissions endpoint. In effect, this means that you will need to grant the permissions via another application first, one that has the Sites.FullControl.All permission added. Going forward, we can expect the process to be streamlined a bit, by introducing a PowerShell cmdlet (similar to how Exchange application access policies are managed) or UI controls.
Without further ado, here’s how to query, add and remove permissions on a given site (collection). The first thing you will need is the siteId, which can be unnecessarily hard to get, as we still don’t have a proper method to enumerate all sites using the Graph API, but that’s a topic for another time. You can use the server-relative URL method or similar, which works in the case of ODFB site collections too. Here’s an example:
Once we have the siteId value, we can query the permissions endpoint. At this time, nothing will be shown, as we haven’t added any entries just yet. To add a permission entry, we then issue a POST request against the /permissions endpoint, specifying the type of permissions we want to add (“read” and “write” are currently supported, with a third value “read and write” mentioned as well), and the application on which we want to grant it. Here’s an example on how the request should look like and a screenshot of the result:
{ "roles": [ "read" ], "grantedToIdentities": [ { "application": { "id": "c89b521f-c8a2-43c6-8600-43d4cadd3372", "displayName": "NewNativeApp" } } ] }
At this point we can actually query the /permissions endpoint to list all the permissions granted. Interestingly enough, using the list method does not return the actual role granted, so you need to query each individual permission entry instead in order to get that information. The important thing of course is whether the granted permissions actually work, so let’s check that. To do so, we now switch to using the other application (the one referenced in the grantedToIdentities element above) which only has Sites.Selected permission granted. If everything works as expected, said application will only be able to run Graph API queries against the single ODFB site collection we added above.
A quick way to test this is by querying the /users/{userId}/drive/root endpoint, but using the /sites/{siteId} one should also do the trick. Once you have obtained a valid token for the application, and optionally confirmed that the Sites.Selected scope is listed, verifying the behavior is easy:
$uri = "https://graph.microsoft.com/v1.0/users/vasil@michev.info/drive/root" $res2 = Invoke-WebRequest -Method Get -Uri $uri -Headers $authHeader2 -Verbose ($res2.Content | ConvertFrom-Json).id 01BATTRZN6Y2GOVW7725BZO354PWSELRRZ $uri = "https://graph.microsoft.com/v1.0/users/pesho@michev.info/drive/root" $res2 = Invoke-WebRequest -Method Get -Uri $uri -Headers $authHeader2 -Verbose ($res2.Content | ConvertFrom-Json).Id Invoke-WebRequest : The remote server returned an error: (403) Forbidden. At line:2 char:9 + $res2 = Invoke-WebRequest -Method Get -Uri $uri -Headers $authHeader2 ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
We allowed access to my own ODFB site collection, so queries run against it complete successfully. For another user’s ODFB site, we get a (403) Forbidden error. Similarly, if we use the /sites endpoint:
$uri = "https://graph.microsoft.com/beta/sites/michev-my.sharepoint.com:/personal/vasil_michev_info" $res2 = Invoke-WebRequest -Method Get -Uri $uri -Headers $authHeader2 -Verbose ($res2.Content | ConvertFrom-Json).id michev-my.sharepoint.com,fb2c7594-e473-451a-85e9-097d3c08307e,89d635a1-2c32-445d-8d61-0b3d22da0690 $uri = "https://graph.microsoft.com/beta/sites/michev-my.sharepoint.com:/personal/pesho_michev_info" $res2 = Invoke-WebRequest -Method Get -Uri $uri -Headers $authHeader2 -Verbose ($res2.Content | ConvertFrom-Json).id Invoke-WebRequest : The remote server returned an error: (403) Forbidden. At line:2 char:20 + $res2 = Invoke-WebRequest -Method Get -Uri $uri -Headers $authHea ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
And there you have it. The application can only query data within a given site (collection), as designated via the permissions entry we added earlier. Using the same method, you can add additional sites as needed, although enumerating all sites is still problematic, and the current approach will probably fail to scale. Going forward, it would be nice to have the ability to block access to given site(s), without having to stamp every other site in the tenant (similarly to how Exchange application access policies behave). But as noted in the original blog post, this is only the first step, so stay tuned.
Oh, just to be on the safe side you probably want to test the “write” role as well. And it’s also worth mentioning that the raw error code (error.code) returned by the HTTP request is accessDenied, not Forbidden, so you might want to adjust your error handling.
UPDATE 08.2022: Microsoft just expanded the scope of Sites.Selected to support the good old SharePoint Online API, as detailed in this blog post. In addition, they’ve confirmed that this method is fully supported for production use, in both flavors!
Great idea to solve the problem of adding sites to the “selection” with the Graph Explorer (?).
Unfortunately, I can’t quite understand it. How did you solve the permissions issue? I can’t even list a site’s permissions. This already requires Sites.FullControl.All. Or does this only work for “personal” ODFB sites?
Until this change it was all or nothing. When we have a PowerShell method it will be easy to scope authorization to a subset of sites. This will make the Azure Authentication for apps useful. Until then we continue to use SharePoint Registration/Authentication method.
Hi,
I think it is a good improvement, since we are starting to receive requests to grant Sharepoint access to some external applications. Just wondering when they will go for the Exchange application access policy model, or adding the same in GUI format (if you select “Sites.Selected” permission, then an option to select which site(s) do you want to apply and level of permission). An interesting feature anyway.