Restrict App Access to SharePoint Online Sites

Exchange Online has a feature called RBAC for Applications to control access to mailboxes. The idea is that the Graph Mail.Read application permission is very powerful. If granted to an app, the app can read messages in every mailbox within a tenant. Sometimes that’s exactly what an app should be able to do, but in many cases, a more restrained view is better. For instance, you might not want apps to go near some sensitive mailboxes, such as those used by corporate executives.

RBAC for Applications solves the problem by creating scopes that dictate the mailboxes an app is allowed to access without needing to be granted access via the all-powerful Mail.Read permission.

Controlling Access to SharePoint Online Sites

Much the same issue exists for SharePoint Online sites. Some sites hold very confidential information that shouldn’t be exposed to app access. The documents should be protected by sensitivity labels that restrict access for file content to specific accounts, but even so, the disclosure of document metadata might be enough to create data leakage, as has become obvious in the way that Copilot for Microsoft can find and use documents unexpectedly.

Like mailboxes, the Graph includes some powerful application permissions that allow access to site content. The Sites.ReadWrite.All, Sites.FullControl.All, and Sites.Manage.All permissions do a wonderful job of allowing unfettered app access to sites, if that’s what you need.

However, in many cases, apps don’t need general access to all sites and instead only require access to a specific site, such as when an app must update items in a list stored in a SharePoint Online site. In these scenarios, the Sites.Selected permission should be used to control access for the app to the target site.

The Sites.Selected permission supports both delegated and application access. The former caters to interactive access when a signed-in user is present. The latter is used by apps, including Microsoft Graph PowerShell SDK sessions in app-only mode.

Adding Access to a Site for an App

To demonstrate how site-specific access works, create a new registered app in the Entra admin center and assign it the Site.Selected application permission. Even after an administrator grants consent for the app to use the permission, it doesn’t mean that the app has access to any sites. It just means that the app is recognized as being able to gain access to specific sites, but only after permission is granted for one or more sites.

This example of granting permission for an app to a site is accomplished using an interactive Microsoft Graph PowerShell SDK session with consent for the Sites.FullControl.All delegated permission. The signed-in user account must also hold a suitable administrative role, such as SharePoint administrator or Global administrator.

First, get the site identifier using the site URL and make sure that you can connect to the target site (to prove that the identifier works):

$Uri = "https://office365itpros.sharepoint.com/sites/Office365Adoption"
$SiteId = $Uri.Split('//')[1].split("/")[0] + ":/sites/" + $Uri.Split('//')[1].split("/")[2]
$Site = Get-MgSite -SiteId $SiteId

Now build the necessary parameters to add the permission. Essentially, the parameters tell SharePoint Online the role to assign to the app and the app details (identifier and display name), plus an optional expiration date. The parameters are then processed by the New-MgSitePermission cmdlet to assign permission to the designated app to access the target site. You can read about the information required in parameters in the Graph Create (site) permissions API.

# The value of $AppId is the application identifier for the app or service principal that you're assigning the permission to
$Application = @{}
$Application.Add("id", $AppId)
$Application.Add("displayName","SPO Site Page App")

$RequestedRole = "write"

$Status = New-MgSitePermission -SiteId $Site.Id -Roles $RequestedRole -GrantedToIdentities @{"application" = $Application}
If ($Status.id) { 
   Write-Host ("{0} permission granted to site {1}" -f $RequestedRole, $Site.DisplayName )
}

Four roles are available:

  • Read: read-only access to the site.
  • Write: read-write access to the site.
  • Manage: manage the site (including lists).
  • Full control (fullcontrol): all permissions, including the ability to set and remove site permissions.

If a permission already exists for an app, New-MgSitePermission overwrites the permission and replaces it with the newly requested permission. For instance, if an app has the manage role and you run New-MgSitePermission to assign the write role, the result is that the capabilities available to the app reduce from manage to write.

To check the current permissions set for the site, run the Get-MgSitePermission cmdlet. The role information is not returned when fetching details for all permission for a site, so the cmdlet must be run twice to retrieve this data:

[array]$Permissions = Get-MgSitePermission -SiteId $Site.Id 
ForEach ($Permission in $Permissions){
   $Data = Get-MgSitePermission -PermissionId $Permission.Id -SiteId $Site.Id -Property Id, Roles, GrantedToIdentitiesV2
   Write-Host ("{0} permission available to {1}" -f ($Data.Roles -join ","), $Data.GrantedToIdentitiesV2.Application.DisplayName)
}
manage permission available to M365Automation
write permission available to SPO Site Page App

The Remove-MgSitePermission cmdlet removes a site permission. For example, this code looks for the permission assigned to an app with a specific name and removes the permission.

[array]$Permissions = Get-MgSitePermission -SiteId $Site.Id 
ForEach ($Permission in $Permissions){
  If ($Permission.GrantedToIdentitiesV2.Application.DisplayName -eq "SPO Site Page App") {
    # Delete the site permission
    Remove-MgSitePermission -SiteId $Site.Id -PermissionId $Permission.Id
    Write-Host "Permission removed"
  } 
}

Testing Access

At this point, the app should have access to the site. To test that everything works, create an app-only Graph SDK by authenticating with the app and a certificate loaded into the app:

Connect-MgGraph -AppId $AppId -TenantId $TenantId -CertificateThumbprint $Thumbprint -NoWelcome

Check that the Get-MgContext cmdlet reports Sites.Selected is listed in the set of scopes available to the app. Figure 1 shows that Sites.Selected is the only scope available to the app. Even so, the app can run cmdlets to interact with site content, such as running Get-MgSitePage to fetch the set of site pages.

Get-MgSitePage -SiteId $Site.Id
Testing restricted app access to SharePoint Online sites.
Figure 1: Testing restricted app access to SharePoint Online sites

Any attempt to access information in another site results in a 403 (forbidden) error.

Managed Identities

RBAC for applications supports Azure automation managed identities, and so does the Sites.Selected permission, even if the fact isn’t well documented. Remember that Azure automation accounts are represented within Entra ID as enterprise apps. Each automation account has an application identifier, so the Sites.Selected permission can be assigned to an automation account just like a registered app. The only difference is that you can’t assign the permission through the Entra admin center GUI and must instead make the assignment for the automation account with PowerShell.

This code finds the service principals for the Graph app and the automation account (M365Automation in this example). Next, it extracts details of the Sites.Selected role from the Graph app before building a hash table containing the information needed for the assignment. Finally, the New-MgServicePrincipalAppRoleAssignment makes the assignment.

$GraphApp = Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'"
$TargetSP = Get-MgServicePrincipal -Filter "displayname eq 'M365Automation'"
$Role = $GraphApp.AppRoles | Where-Object {$_.Value -eq "Sites.Selected"}
$AppRoleAssignment = @{}
$AppRoleAssignment.Add("PrincipalId",$TargetSP.Id)
$AppRoleAssignment.Add("ResourceId",$GraphApp.Id)
$AppRoleAssignment.Add("AppRoleId",$Role.Id)
$RoleAssignment = New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $TargetSP.Id -BodyParameter $AppRoleAssignment
If ($RoleAssignment.AppRoleId) {
  Write-Host ("{0} permission granted to {1}" -f $Role.Value, $TargetSP.DisplayName)
}

You can now assign the site permission as shown above, using the app identifier for the automation account.

I removed the Sites.ReadWrite.All permission from an automation account and replaced it with Sites.Selected. The automation account supports several runbooks, including one to update items in a SharePoint list. After granting the write role to the automation account for the site that holds the target list, the site, the runbook was able to write new items into the list at its next scheduled run.

Apply Least Privilege – Including to Lists and Individual Files

Management operations will often require full access to all SharePoint sites in a tenant. But when tasks are targeted on specific sites, it’s best to apply the principle of least privilege and use Sites.Selected to restrict the relevant apps to a minimum set of sites.

Now that you know how to use the Sites.Selected permission to restrict app access to SharePoint Online sites, you can consider further restricting app access to individual lists and files. SharePoint Online’s granular access is improving and the need to assign the Sites.ReadWrite.All permission is reducing. Is it time to review tenant apps to determine if permission restriction is possible?

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.

Leave a Reply