|
| 1 | +--- |
| 2 | +title: Registering and using Remote Event Receivers without having a dependency on Azure ACS |
| 3 | +description: Explains how Remote Event Receivers can be registered using an Entra application (Azure AD) and as such are not dependent on Azure ACS. |
| 4 | +ms.date: 03/14/2024 |
| 5 | +ms.localizationpriority: high |
| 6 | +ms.service: sharepoint |
| 7 | +--- |
| 8 | + |
| 9 | +# Registering and using Remote Event Receivers without having a dependency on Azure ACS |
| 10 | + |
| 11 | +The classical usage of Remote Event Receivers, which we'll name RERs in the remainder of this page, is strongly tied to Azure ACS. Key RER use cases are being part of a provider hosted SharePoint Add-Ins or being used outside of SharePoint Add-Ins by registering them using an Azure ACS principal as authentication means. With the announced [retirement of Azure ACS](https://aka.ms/retirement/acs/support) and the [retirement of SharePoint Add-Ins](https://aka.ms/retirement/addins/support), RERs depending on Azure ACS will stop working as they'll follow the outlined Azure ACS retirement path. |
| 12 | + |
| 13 | +However, there's an option to use RERs via an Entra application, so without having a dependency on Azure ACS. In the coming chapters you'll learn how to configure the Entra app for registering the RERs and you'll learn more about the differences between a RER registered using Azure ACS versus one registered using an Entra app. |
| 14 | + |
| 15 | +> [!Important] |
| 16 | +> Although these RERs do not depend on Azure ACS they still will retire, the main difference is that they'll keep working until July 1, 2027 and that they'll also work for new tenants onboarding after November 1, 2024. RERs depending on Azure ACS will follow the Azure ACS retirement path, so they'll stop working on April 2, 2026 and for new tenants onboarding after November 1, 2024 they'll not work anymore. |
| 17 | +
|
| 18 | +## Step 1: Configure your Entra application for registering a RER |
| 19 | + |
| 20 | +It's required for the Entra application that's used to register the RERs to be configured with the `sites.selected` permission role. Follow this [blog post](https://techcommunity.microsoft.com/t5/microsoft-sharepoint-blog/develop-applications-that-use-sites-selected-permissions-for-spo/ba-p/3790476) to learn more on how to configure your Entra app. In our case, since the registration of RERs requires SharePoint REST/CSOM APIs it's important to also add the SharePoint `sites.selected` role and to configure the application with a certificate as the `sites.selected` role only is there when application permissions are used. Calling the SharePoint REST/CSOM APIs using application permissions requires the use of a certificate. |
| 21 | + |
| 22 | +## Step 2: Register a RER using your Entra application |
| 23 | + |
| 24 | +Once the Entra application is ready use it to authenticate to SharePoint using application permissions followed by using the SharePoint REST/CSOM APIs to register the RERs you need. You can for example do a POST to `_api/Web/EventReceivers` or use the equivalent CSOM `EventReceivers` collection of a `Web` and add a new one. If you prefer to use PnP PowerShell then below snippet can be used: |
| 25 | + |
| 26 | +```PowerShell |
| 27 | +# Connect via the created Entra app using application permissions |
| 28 | +Connect-PnPOnline https://contoso.sharepoint.com/sites/testsite -ClientId 3b9ad858-dbbb-489b-b63d-1905426222f8 -Tenant contoso.onmicrosoft.com -CertificatePath ".\RERApp.pfx" |
| 29 | +
|
| 30 | +# Add a RER for synchronous firing on item add. This RER is calling a ngrok URL to proxy back to an Azure function running on localhost |
| 31 | +Add-PnPEventReceiver -List "MyList" -Name "RER-HelloWorld-ItemAdding" -Url "https://0051-84-195-208-70.ngrok-free.app/api/Service1" -SequenceNumber 10000 -EventReceiverType ItemAdding -Synchronization Synchronous |
| 32 | +
|
| 33 | +# List all added RERs |
| 34 | +Get-PnPEventReceiver -List "MyList" |
| 35 | +
|
| 36 | +# Delete a RER |
| 37 | +Remove-PnPEventReceiver -List "MyList" -Identity "<replace by RER guid from previous command output" |
| 38 | +``` |
| 39 | + |
| 40 | +## Step 3: Learn more about the difference between a RER registered using an Entra application with sites.selected versus a RER registered using Azure ACS |
| 41 | + |
| 42 | +When a RER is registered using en Entra app with `sites.selected` it will fire for both synchronous and asynchronous events, but there is a key difference to be aware of: when your RER service is called you'll not receive a `ContextToken` anymore. Consequence is that your RER service cannot impersonate the user that triggered the RER to fire but it has to use application permissions to call back to SharePoint. |
| 43 | + |
| 44 | +Below snippet shows the payload sent to your service, notice the "empty" `ContextToken` element and the "GetContextTokenError value for the `ErrorCode` element. |
| 45 | + |
| 46 | +```xml |
| 47 | +<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> |
| 48 | + <s:Body> |
| 49 | + <ProcessOneWayEvent xmlns="http://schemas.microsoft.com/sharepoint/remoteapp/"> |
| 50 | + <properties xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> |
| 51 | + <AppEventProperties i:nil="true"/> |
| 52 | + <ContextToken/> |
| 53 | + <CorrelationId>25c1f6a0-e0d9-7000-ce2b-c721e30ad4bd</CorrelationId> |
| 54 | + <CultureLCID>2067</CultureLCID> |
| 55 | + <EntityInstanceEventProperties i:nil="true"/> |
| 56 | + <ErrorCode>GetContextTokenError</ErrorCode> |
| 57 | + <ErrorMessage>The endpoint address 'https://0f68-84-195-208-70.ngrok-free.app/api/Service1' does not match the app's endpoint 'www.contoso.com'.</ErrorMessage> |
| 58 | + <EventType>ItemAdded</EventType> |
| 59 | + <ItemEventProperties> |
| 60 | + <AfterProperties xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays"> |
| 61 | + <a:KeyValueOfstringanyType> |
| 62 | + <a:Key>TimesInUTC</a:Key> |
| 63 | + <a:Value i:type="b:string" |
| 64 | + xmlns:b="http://www.w3.org/2001/XMLSchema">TRUE</a:Value> |
| 65 | + </a:KeyValueOfstringanyType> |
| 66 | + <a:KeyValueOfstringanyType> |
| 67 | + <a:Key>ContentType</a:Key> |
| 68 | + <a:Value i:type="b:string" |
| 69 | + xmlns:b="http://www.w3.org/2001/XMLSchema">Item</a:Value> |
| 70 | + </a:KeyValueOfstringanyType> |
| 71 | + <a:KeyValueOfstringanyType> |
| 72 | + <a:Key>Title</a:Key> |
| 73 | + <a:Value i:type="b:string" |
| 74 | + xmlns:b="http://www.w3.org/2001/XMLSchema">demo1</a:Value> |
| 75 | + </a:KeyValueOfstringanyType> |
| 76 | + <a:KeyValueOfstringanyType> |
| 77 | + <a:Key>FileSystemObjectType</a:Key> |
| 78 | + <a:Value i:type="b:string" |
| 79 | + xmlns:b="http://www.w3.org/2001/XMLSchema">File</a:Value> |
| 80 | + </a:KeyValueOfstringanyType> |
| 81 | + </AfterProperties> |
| 82 | + <AfterUrl i:nil="true"/> |
| 83 | + <BeforeProperties xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/> |
| 84 | + <BeforeUrl/> |
| 85 | + <CurrentUserId>6</CurrentUserId> |
| 86 | + <ExternalNotificationMessage i:nil="true"/> |
| 87 | + <IsBackgroundSave>false</IsBackgroundSave> |
| 88 | + <ListId>1814779c-276b-4380-adaa-42794f7d08c3</ListId> |
| 89 | + <ListItemId>2</ListItemId> |
| 90 | + <ListTitle>RER</ListTitle> |
| 91 | + <UserDisplayName>Joe Doe</UserDisplayName> |
| 92 | + < UserLoginName>i:0#.f|membership| [email protected]</ UserLoginName> |
| 93 | + <Versionless>false</Versionless> |
| 94 | + <WebUrl>https://contoso.sharepoint.com/sites/testsite</WebUrl> |
| 95 | + </ItemEventProperties> |
| 96 | + <ListEventProperties i:nil="true"/> |
| 97 | + <SecurityEventProperties i:nil="true"/> |
| 98 | + <UICultureLCID>1033</UICultureLCID> |
| 99 | + <WebEventProperties i:nil="true"/> |
| 100 | + </properties> |
| 101 | + </ProcessOneWayEvent> |
| 102 | + </s:Body> |
| 103 | +</s:Envelope> |
| 104 | +``` |
| 105 | + |
| 106 | +## Step 4: Code sample to help you understand how to use RERs |
| 107 | + |
| 108 | +RERs can be implemented as modern .NET solutions using Azure Functions, below snippet shows a RER implementation that rejects an item add as part of a synchronous ItemAdding event. |
| 109 | + |
| 110 | +```csharp |
| 111 | +using System; |
| 112 | +using System.IO; |
| 113 | +using System.Threading.Tasks; |
| 114 | +using Microsoft.AspNetCore.Mvc; |
| 115 | +using Microsoft.Azure.WebJobs; |
| 116 | +using Microsoft.Azure.WebJobs.Extensions.Http; |
| 117 | +using Microsoft.AspNetCore.Http; |
| 118 | +using Microsoft.Extensions.Logging; |
| 119 | +using Newtonsoft.Json; |
| 120 | +using System.Net; |
| 121 | + |
| 122 | +namespace AzureFunctionRER |
| 123 | +{ |
| 124 | + public static class Service1 |
| 125 | + { |
| 126 | + |
| 127 | + private static string cancelResponse = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body><ProcessEventResponse xmlns=\"http://schemas.microsoft.com/sharepoint/remoteapp/\"><ProcessEventResult xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><ChangedItemProperties xmlns:a=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\"/><ErrorMessage>You shall not pass!</ErrorMessage><RedirectUrl i:nil=\"true\"/><Status>CancelWithError</Status></ProcessEventResult></ProcessEventResponse></s:Body></s:Envelope>"; |
| 128 | + |
| 129 | + [FunctionName("Service1")] |
| 130 | + public static async Task<IActionResult> Run( |
| 131 | + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, |
| 132 | + ILogger log) |
| 133 | + { |
| 134 | + string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); |
| 135 | + |
| 136 | + // For cancelling a sync event |
| 137 | + return new ContentResult |
| 138 | + { |
| 139 | + Content = cancelResponse, |
| 140 | + ContentType = "text/xml", |
| 141 | + StatusCode = (int)HttpStatusCode.InternalServerError |
| 142 | + }; |
| 143 | + |
| 144 | + // For accepting the sync event |
| 145 | + //string responseMessage = string.IsNullOrEmpty(name) |
| 146 | + // ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response." |
| 147 | + // : $"Hello, {name}. This HTTP triggered function executed successfully."; |
| 148 | +
|
| 149 | + //return new OkObjectResult(responseMessage); |
| 150 | + } |
| 151 | + } |
| 152 | +} |
| 153 | +``` |
0 commit comments