Scenario
You administer Microsoft 365 SharePoint Online. Part of your daily activities is providing Microsoft Graph and SharePoint Sites.Selected API permissions to other users (developers).
In Aug/Sep 2023 Microsoft pushed an update that prevents site collection admins to create or update an Azure Access Control (ACS) principal (that was the way most of developers used to get Client Id and Client secret to access SharePoint site). So your users are probably getting something like Your SharePoint tenant admin doesn’t allow site collection admins to create or update an Azure Access Control (ACS) principal message attempting to create or update SharePoint App-only principal at AppRegNew.aspx or AppInv.aspx pages. Here are more details on the issue.
Microsoft and MVPs shared some technique how to provide Sites.Selected API permissions, but dealing with scripts manually, elevating individual permissions every time you need to run the script – it all takes time and not very efficient. More and more devs are reaching you on the app. So you want to automate this process.
Solution
Solution architecture
My way to automate it includes:
- Using Azure App registration as service principal
with API permissions configured - SharePoint list as a frontend
here you can accept intake requests, organize approval workflow and display automation results - Azure Function App as a backend
here will be your PowerShell script hosted that runs on scheduled basis and takes care of actual permissions provisioning
Solution details
High-level, getting application permissions to some specific SharePoint site is a two-step process:
- get application registration in Azure and properly configure it
- get permissions for this application to a specific SharePoint site
For the first step – check this and this articles. I’ll focus on the second step below.
You can provide Sites.Selected permissions for the app to a site with
- Graph API Site Permissions resource (see also PowerShell code samples)
- PnP.PowerShell: Grant-PnPAzureADAppSitePermission
I will be using second one one. Also PnP.PowerShell will be used to get access to SharePoint intake site and read/update requests from SharePoint list and so on.
Azure App Registration
I registered an admin Application in Azure – “SharePoint Automation App”, added Graph Sites.FullControl.All and SharePoint Sites.FullControl.All permissions, then added Microsoft Graph Directory.Read.All permissions and got tenant admin consent:
I generated a self-signed certificate and added it to the app:
This app will be used to call provide permissions, and to connect to the SharePoint front-end.
Users will register their applications in Azure, add Graph Sites.Selected and SharePoint Sites.Selected permissions, got tenant admin consent, then request permissions to the specific site by creating an intake request – new list item.
Front-End SharePoint Site
I created a SharePoint site for automation. This site will play a front-end role for users. I created a list “Sites.Selected” and updated list columns so I have the following fields:
- Target Site Url
- Application Id
- Permissions (read/write)
- Automation Output
In real-world (Prod) – You can (should) also implement approval workflow as you’d provide permissions for the application to the site only with this site owner approval. The PowerShell code behind should also validate site owner’s consent with app access to site. But for the sake of simplicity I’ll skip this in my demo.
Azure Function App
I created an Azure Function App with the following parameters:
– Runtime stack: PowerShell Core
– Version: 7.2.
– OS: Windows
– Hosting plan: Consumption
And then PowerShell timer-triggered function in Visual Studio Code.
Function requirements.psd1 (it takes a few hours for Azure to install modules; while modules are installing – you might see “[Warning] The first managed dependency download is in progress, function execution will continue when it’s done. Depending on the content of requirements.psd1, this can take a few minutes. Subsequent function executions will not block and updates will be performed in the background.”):
@{
'Az' = '10.*'
'PnP.PowerShell' = '2.*'
}
Azure Az module to access other Azure resources. PnP.PowerShell module will be used to access SharePoint.
I will keep my admin Azure registered app in a key vault, so need somehow to let the key vault know that this specific app can access this specific credentials. So I enabled system assigned managed Identity for the Function App:
MS: “This resource is registered with Azure Active Directory. The managed identity can be configured to allow access to other resources…”.
I’m going to use an object (principal) Id of this function to grant access to keyvault.
Azure key vault
Surely we do not hard-code app secrets. So we need a key vault o store app credentials.
I created a key vault under the same resource group in Azure and named it “SharePointAutomationDemo”. Then I added a roles assignment – “Key Vault Secret User” and “Key vault Reader” to the Function App via it’s managed identity:
I also assigned “Key Vault Administrator” role to the user (developer) who will add certificates/secrets to this key vault and develop Azure function code.
With the new ‘Lists.SelectedOperations.Selected’, ‘ListItems.SelectedOperations.Selected’ and ‘Files.SelectedOperations.Selected’ permissions it is new possible to provide application permissions at a specific list, library or list item levels or at a particular document level, so automation solution would be a little more complicated.
Code repository
https://github.com/VladilenK/Sites.Selected-Automation
Videos
Part 1: Getting Azure App Registration with Sites.Selected API Permissions
Part 2: SharePoint and Microsoft Graph API Sites.Selected permissions provisioning automation