|
| 1 | +--- |
| 2 | +title: Create Azure Functions for SharePoint webhooks using an azd template |
| 3 | +description: Use Azure Developer cli (azd) to deploy an Azure function app that connects to your SharePoint Online tenant, to register and manage webhooks, and process the notifications from SharePoint. |
| 4 | +ms.date: 02/27/2025 |
| 5 | +ms.localizationpriority: low |
| 6 | +--- |
| 7 | +# Azure Functions for SharePoint webhooks using azd |
| 8 | + |
| 9 | +[Azure Developer CLI (azd)](https://aka.ms/azd) is an open-source tool that accelerates provisioning and deploying app resources in Azure. |
| 10 | + |
| 11 | +This article uses the [Azure function app for SharePoint webhooks public template](https://github.com/Azure-Samples/azd-functions-sharepoint-webhooks) to deploy an Azure function app that connects to your SharePoint Online tenant, to register and manage [webhooks](overview-sharepoint-webhooks.md), and process the notifications from SharePoint. |
| 12 | + |
| 13 | +## Prerequisites |
| 14 | + |
| 15 | +- [Node.js 20](https://www.nodejs.org/) |
| 16 | +- [Azure Functions Core Tools](/azure/azure-functions/functions-run-local) |
| 17 | +- [Azure Developer CLI (azd)](/azure/developer/azure-developer-cli/install-azd) |
| 18 | +- An Azure subscription that trusts the same Microsoft Entra ID directory as the SharePoint tenant |
| 19 | + |
| 20 | +## Permissions required to provision the resources in Azure |
| 21 | + |
| 22 | +The account running **azd** must have at least the following roles to successfully provision the resources: |
| 23 | + |
| 24 | +- Azure role **[Contributor](/azure/role-based-access-control/built-in-roles/privileged#contributor)**: To create all the resources needed |
| 25 | +- Azure role **[Role Based Access Control Administrator](/azure/role-based-access-control/built-in-roles/privileged#role-based-access-control-administrator)**: To assign roles (to access the storage account and Application Insights) to the managed identity of the function app |
| 26 | + |
| 27 | +## Deploy the function app in Azure |
| 28 | + |
| 29 | +1. Run **azd init** from an empty local (root) folder: |
| 30 | + |
| 31 | + ```console |
| 32 | + azd init --template azd-functions-sharepoint-webhooks |
| 33 | + ``` |
| 34 | + |
| 35 | + Enter an environment name, such as **spofuncs-quickstart** when prompted. In **azd**, the environment is used to maintain a unique deployment context for your app. |
| 36 | + |
| 37 | +1. Open the file **infra/main.parameters.json**, and set the variables `TenantPrefix` and `siteRelativePath` to match your SharePoint tenant. |
| 38 | + |
| 39 | + Review the article on [Manage environment variables](/azure/developer/azure-developer-cli/manage-environment-variables) to manage the azd's environment variables. |
| 40 | + |
| 41 | +1. Finally, run the command **azd up** to build the app, provision the resources in Azure and deploy the app package. |
| 42 | + |
| 43 | +## Grant the function app access to SharePoint Online |
| 44 | + |
| 45 | +The authentication to SharePoint is done using `DefaultAzureCredential`, so the credential used depends on whether the function app runs locally, or in Azure. |
| 46 | + |
| 47 | +If you never heard about `DefaultAzureCredential`, you should familiarize yourself with its concept by referring to the section **Use DefaultAzureCredential for flexibility** in [Credential chains in the Azure Identity client library for JavaScript](/azure/developer/javascript/sdk/authentication/credential-chains). |
| 48 | + |
| 49 | +### Using its managed identity |
| 50 | + |
| 51 | +`DefaultAzureCredential` will use a managed identity to authenticate to SharePoint. This may be the existing, system-assigned managed identity of the function app service or a user-assigned managed identity. |
| 52 | + |
| 53 | +This tutorial assumes the system-assigned managed identity is used. |
| 54 | + |
| 55 | +#### Grant the SharePoint API permission Sites.Selected to the managed identity |
| 56 | + |
| 57 | +Navigate to your function app in the [Azure portal](https://portal.azure.com/#blade/HubsExtension/BrowseResourceBlade/resourceType/Microsoft.Web%2Fsites/kind/functionapp) > select **Identity** and note the **Object (principal) ID** of the system-assigned managed identity. |
| 58 | + |
| 59 | +In this tutorial, it is **d3e8dc41-94f2-4b0f-82ff-ed03c363f0f8**. |
| 60 | + |
| 61 | +Then, use one of the scripts below to grant this identity the app-only permission **Sites.Selected** on the SharePoint API: |
| 62 | + |
| 63 | +> [!IMPORTANT] |
| 64 | +> The scripts below require at least the delegated permission [`AppRoleAssignment.ReadWrite.All`](/graph/permissions-reference#approleassignmentreadwriteall) (requires admin consent) |
| 65 | + |
| 66 | +<details> |
| 67 | + <summary>Using the Microsoft Graph PowerShell SDK</summary> |
| 68 | + |
| 69 | +```powershell |
| 70 | +# This script requires the modules Microsoft.Graph.Authentication, Microsoft.Graph.Applications, Microsoft.Graph.Identity.SignIns, which can be installed with the cmdlet Install-Module below: |
| 71 | +# Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Applications, Microsoft.Graph.Identity.SignIns -Scope CurrentUser -Repository PSGallery -Force |
| 72 | +Connect-MgGraph -Scope "Application.Read.All", "AppRoleAssignment.ReadWrite.All" |
| 73 | +$managedIdentityObjectId = "d3e8dc41-94f2-4b0f-82ff-ed03c363f0f8" # 'Object (principal) ID' of the managed identity |
| 74 | +$scopeName = "Sites.Selected" |
| 75 | +$resourceAppPrincipalObj = Get-MgServicePrincipal -Filter "displayName eq 'Office 365 SharePoint Online'" # SPO |
| 76 | +$targetAppPrincipalAppRole = $resourceAppPrincipalObj.AppRoles | ? Value -eq $scopeName |
| 77 | + |
| 78 | +$appRoleAssignment = @{ |
| 79 | + "principalId" = $managedIdentityObjectId |
| 80 | + "resourceId" = $resourceAppPrincipalObj.Id |
| 81 | + "appRoleId" = $targetAppPrincipalAppRole.Id |
| 82 | +} |
| 83 | +New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $managedIdentityObjectId -BodyParameter $appRoleAssignment | Format-List |
| 84 | +``` |
| 85 | +</details> |
| 86 | + |
| 87 | +<details> |
| 88 | + <summary>Using az cli in Bash</summary> |
| 89 | + |
| 90 | +```bash |
| 91 | +managedIdentityObjectId="d3e8dc41-94f2-4b0f-82ff-ed03c363f0f8" # 'Object (principal) ID' of the managed identity |
| 92 | +resourceServicePrincipalId=$(az ad sp list --query '[].[id]' --filter "displayName eq 'Office 365 SharePoint Online'" -o tsv) |
| 93 | +resourceServicePrincipalAppRoleId="$(az ad sp show --id $resourceServicePrincipalId --query "appRoles[?starts_with(value, 'Sites.Selected')].[id]" -o tsv)" |
| 94 | + |
| 95 | +az rest --method POST --uri "https://graph.microsoft.com/v1.0/servicePrincipals/${managedIdentityObjectId}/appRoleAssignments" --headers 'Content-Type=application/json' --body "{ 'principalId': '${managedIdentityObjectId}', 'resourceId': '${resourceServicePrincipalId}', 'appRoleId': '${resourceServicePrincipalAppRoleId}' }" |
| 96 | +``` |
| 97 | +</details> |
| 98 | + |
| 99 | +#### Grant the managed identity effective access to a SharePoint site |
| 100 | + |
| 101 | +Navigate to the [Enterprise applications](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/) > Set the **Application type** filter to **Managed Identities** > select your managed identity and note its **Application ID**. |
| 102 | + |
| 103 | +> [!NOTE] |
| 104 | +> In this tutorial, it is **3150363e-afbe-421f-9785-9d5404c5ae34**. |
| 105 | +
|
| 106 | +Then, use one of the scripts below to grant it the app-only permission **manage** (minimum required to register a webhook) on a specific SharePoint site: |
| 107 | + |
| 108 | +> [!IMPORTANT] |
| 109 | +> The app registration used to run those scripts must have at least the following permissions: |
| 110 | +> |
| 111 | +> - Delegated permission **Application.ReadWrite.All** in the Graph API (requires admin consent) |
| 112 | +> - Delegated permission **AllSites.FullControl** in the SharePoint API (requires admin consent) |
| 113 | +
|
| 114 | +<details> |
| 115 | + <summary>Using PnP PowerShell</summary> |
| 116 | + |
| 117 | +[PnP PowerShell](https://pnp.github.io/powershell/cmdlets/Grant-PnPAzureADAppSitePermission.html) |
| 118 | + |
| 119 | +```powershell |
| 120 | +Connect-PnPOnline -Url "https://YOUR_SHAREPOINT_TENANT_PREFIX.sharepoint.com/sites/YOUR_SHAREPOINT_SITE_NAME" -Interactive -ClientId "YOUR_PNP_APP_CLIENT_ID" |
| 121 | +Grant-PnPAzureADAppSitePermission -AppId "3150363e-afbe-421f-9785-9d5404c5ae34" -DisplayName "YOUR_FUNC_APP_NAME" -Permissions Manage |
| 122 | +``` |
| 123 | +</details> |
| 124 | + |
| 125 | +<details> |
| 126 | + <summary>Using m365 cli in Bash</summary> |
| 127 | + |
| 128 | +[m365 cli](https://pnp.github.io/cli-microsoft365/cmd/spo/site/site-apppermission-add/) |
| 129 | + |
| 130 | +```bash |
| 131 | +targetapp="3150363e-afbe-421f-9785-9d5404c5ae34" |
| 132 | +siteUrl="https://YOUR_SHAREPOINT_TENANT_PREFIX.sharepoint.com/sites/YOUR_SHAREPOINT_SITE_NAME" |
| 133 | +m365 spo site apppermission add --appId $targetapp --permission manage --siteUrl $siteUrl |
| 134 | +``` |
| 135 | +</details> |
| 136 | + |
| 137 | +## Call the function app |
| 138 | + |
| 139 | +For security reasons, when running in Azure, the function app requires an app key to pass in the query string parameter **code**. The app keys are found in the function app service's **App Keys** keys page. |
| 140 | + |
| 141 | +Most HTTP functions take optional parameters `TenantPrefix` and `siteRelativePath`. If they are not specified, the values in the app's environment variables are used. |
| 142 | + |
| 143 | +Below is a sample script in PowerShell to call the function app: |
| 144 | + |
| 145 | +```powershell |
| 146 | +# Edit those variables to match your environment |
| 147 | +$funchost = "YOUR_FUNC_APP_NAME" |
| 148 | +$code = "YOUR_HOST_KEY" |
| 149 | +$listTitle = "YOUR_SHAREPOINT_LIST" |
| 150 | +$notificationUrl = "https://${funchost}.azurewebsites.net/api/webhooks/service?code=${code}" |
| 151 | +
|
| 152 | +# List all the webhooks registered on a list |
| 153 | +Invoke-RestMethod -Method GET -Uri "https://${funchost}.azurewebsites.net/api/webhooks/list?code=${code}&listTitle=${listTitle}" |
| 154 | +
|
| 155 | +# Register a webhook in a list |
| 156 | +Invoke-RestMethod -Method POST -Uri "https://${funchost}.azurewebsites.net/api/webhooks/register?code=${code}&listTitle=${listTitle}¬ificationUrl=${notificationUrl}" |
| 157 | +
|
| 158 | +# Show this webhook registered on a list |
| 159 | +Invoke-RestMethod -Method GET -Uri "https://${funchost}.azurewebsites.net/api/webhooks/show?code=${code}&listTitle=${listTitle}¬ificationUrl=${notificationUrl}" |
| 160 | +
|
| 161 | +# Remove the webhook from a list |
| 162 | +# Step 1: Call the function /webhooks/show to get the webhook id |
| 163 | +$webhookId = $(Invoke-RestMethod -Method GET -Uri "https://${funchost}.azurewebsites.net/api/webhooks/show?code=${code}&listTitle=${listTitle}¬ificationUrl=${notificationUrl}").Id |
| 164 | +# Step 2: Call the function /webhooks/remove and pass the webhook id |
| 165 | +Invoke-RestMethod -Method POST -Uri "https://${funchost}.azurewebsites.net/api/webhooks/remove?code=${code}&listTitle=${listTitle}&webhookId=${webhookId}" |
| 166 | +``` |
| 167 | + |
| 168 | +## Cleanup the resources in Azure |
| 169 | + |
| 170 | +You can delete all the resources this project created in Azure, by running the command **azd down**. |
| 171 | + |
| 172 | +Alternatively, you can delete the resource group, that has the azd environment's name by default. |
| 173 | + |
| 174 | +## See also |
| 175 | + |
| 176 | +- [Overview of SharePoint webhooks](overview-sharepoint-webhooks.md) |
0 commit comments