Subscribing to Azure AD Change Notifications with PowerShell

Microsoft Graph webhooks or the ability to receive an Azure AD Change Notification has been around for some time. However, as I primarily deal with Azure AD for user and group objects I never previously had the need to utilize them. Using Azure AD delta queries was always enough. Until recently, whereby I needed a solution that could be triggered by changes to Azure AD Guest objects. Azure AD Change Notifications is the answer.

Overview

This post details an example solution to get started with Azure AD Change Notifications. It covers;

  • Change Notification Subscription creation
  • Building a set of Azure PowerShell Functions to receive, validate and manage change notifications along with updating a change notification subscription
  • Example logic to take a change notification and perform business logic with it

The diagram below shows an overview of the example detailed in this post.

Azure AD Change Notification Configuration Example

Understanding Azure AD Change Notifications

  • By default, change notifications only contain the reference to the resource associated with the notification. However, we can specify that the resource data is also supplied. This would mean that we don’t then need to query the resource to get the full object.
    • Currently for the Users resource type when specifying to ‘IncludeResourceData‘ the following is returned
      • Azure Active Directory workload is not enabled for rich notifications.
    • The documentation does state that for /users resource data can’t be included in notifications, but keep it in mind for change notifications on other object types
  • Not all Azure AD / Microsoft Office365 resources support change notifications. Azure AD Groups and Users are supported. Also, Microsoft Office365 services such as Outlook, SharePoint, Teams and ToDo.
  • There are limits to the number of notification subscriptions you can have. Within a Tenant (for all applications combined), the limit is 1000 subscriptions.
  • A subscription has a lifetime. The subscription must be renewed before it expires for it to be maintained. The notification lifetime differs based on resource type.

Azure AD Change Notifications Azure Functions

The example in this post utilizes three Azure PowerShell Functions. I’ve implemented three separate functions so that each performs specific functionality. The three Azure Functions are;

  1. Scheduled Timer Function – Subscription Management
    • every 12 hours the scheduled timer function will renew the Azure AD Change Notification Subscription
  2. HTTP Trigger Function – Receive and Validate Notifications
    • receives change notifications from the Azure AD Change Notification Subscription.
      • it is the URL of this Function that is configured as the NotificationURL on the Azure AD Change Notification Subscription
    • acknowledges that Azure AD Change Notifications are received
    • forwards events to the third and final Function
    • sends a notification with a summary of the change notification (an email via SendGrid)
  3. HTTP Trigger Function – Perform Business Logic
    • triggered by the change notification and the Azure Function listed directly above
    • performs an Azure AD Query for the changed object
    • performs business logic (in this example sends an email via SendGrid)

Create an Azure Function App

Using the Azure Portal create a new Function App that will host the Azure Functions. VSCode will be used to create and publish the Azure Functions using PowerShell.

Azure AD Change Notification Function App

For this example, I’m using the consumption plan. With this plan there are not a large number of resources associated with it. And as two of the Functions interoperate with each other the Azure Function Application Settings will need to be updated for PowerShell Concurrency. I posted about this in this topic, and I am using 4 again successfully for the configuration.

After validation completes the Function App can be created.

Configuring VSCode for Azure Functions Development

In your file explorer create the folder where you will locate your Azure Functions locally. In VSCode open that folder and ensure that your Terminal is using PowerShell (formally PowerShell Core, and NOT Windows PowerShell). Ensure you have the latest Azure Functions Core Tools by running the following command.

npm install -g azure-functions-core-tools@3 --unsafe-perm true
Azure AD Change Notification Azure PowerShell Function

Create the Receive Change Notification Azure Function

The first Azure Function that we will create is the one that receives the Azure AD Change Notifications and sends validation of receipt. Create a New Local Project.

Azure AD Change Notification Function App

Choose PowerShell as the language

As this function will receive the Azure AD Change Notifications make it an HTTP Trigger Function.

Azure AD Change Notification Azure PowerShell HTTP Trigger Function

I’ve named my function “ReceiveChangeNotification

And configured the Authorization level as Function.

With the Azure Function created, expand the Local Project => Functions, select the new Function, expand the function name and select run.ps1.

Press F5 to run the Function App locally.

Call the function app from PowerShell using the following command (assuming that your local instance is also running on Port 7071, and you named your function the same as mine (ReceiveChangeNotification).

Invoke-RestMethod -Method Post -Uri http://localhost:7071/api/ReceiveChangeNotification -body '{"name": "Darren"}' -ContentType "application/json"

If you’ve configured everything correctly you should see the following response from calling the new Azure Function.

Azure AD Change Notification Azure PowerShell Function

Setup Twilio SendGrid

For this example, I’m going to send event information as an email. I will use the SendGrid output binding to do this. I’ve previously written about enabling Twilio SendGrid for Azure Functions. This integration will use the same. Twilio SendGrid will be used to send an email notification when an Azure AD Change Notification is received. It will contain the details of the Azure AD User Object that has been updated.
Using the Azure Portal whilst in the Resource Group associated with your Azure Function App, from the Azure Marketplace add Twilio SendGrid to your environment then:

  • after creation of the SendGrid subscription, use the Configure account now link to validate your SendGrid Account and verify your Sender Identity.
  • You will also need to generate a SendGrid API Key that will be used in your Function to integrate with SendGrid

The following shows the configuration of Twilio SendGrid when added from the Azure Marketplace. I’m using the Free 100 Monthly plan for this example.

Azure AD Change Notification Azure PowerShell Function SendGrid integration

After Twilio SendGrid is deployed select the Configure account now button to continue the configuration in the Twilio Portal.

Create Single Sender Verification with a From and Reply address for your environment. I’m using the same address for both From and Reply. Once configured, go to the associated Inbox for the email account and verify the address.

Create a SendGrid API Key

Finally, we need an API Key for SendGrid to be able to integrate our Azure Function Apps SendGrid output bindings. I used the CURL example integration to trigger the flow to allow configuration of an API key.

Azure AD Change Notification Azure PowerShell Function SendGrid API Integration

Add SendGrid as an Azure Function Output Binding

We now need to configure an Azure Function for our SendGrid configuration.

  • In the local.settings.json file for your Azure Function add a variable with your SendGrid API key. In my example I named mine ‘sendGrid‘. Also add a variable for the From and To Addresses. I’ve named them sendGridToAddress and sendGridFromAddress. The From address needs to match the address configured in the SendGrid configuration portal.
Azure PowerShell Function local.settings.json SendGrid API Key variable
  • right click on your Function in your local project and select Add Binding
  • Select Out => SendGrid
Add SendGrid Output Binding
Add SendGrid Output Binding
  • accept ‘message‘ as the name to identify the binding
Message as the SendGrid Output Binding object name
Message as the SendGrid Output Binding object name
  • Select the Application Setting ‘sendGrid‘ that contains your SendGrid API key as configured earlier.
Azure Function App SendGrid API Key Variable
Azure Function App SendGrid API Key Variable
  • press enter to leave blank “To address“, “From address“, “Message Subject” and “Message Text” as we’ll specify these in the Azure Function.
  • The SendGrid Output Binding is then be added to your Azure Function

Open the run.ps1 file for the ReceiveChangeNotification Azure PowerShell Function. Replace it with the following script. It contains the logic to reply to a new subscription, reply to change notifications and send a summary email to the configured (in your local.settings.json) email address.

Deploy the Azure Function

Now it’s time to deploy the Azure Function to Azure. After selecting Deploy and selecting the Function App that you created earlier it will deploy to Azure. After deployment it will display a notification asking whether to also send the local settings configuration. Select Yes.

Azure AD Change Notification Azure PowerShell Function deploy to Azure

Create the Azure AD Change Notification Subscription

Azure AD Change Notifications Subscriptions are created under the context of a user. I strongly recommend creating a dedicated Azure AD User in the associated Azure Tenant that will be used to create and maintain the change notifications subscription. After creating the Azure AD User account that will be used for the Azure AD Change Notification Subscriptions login with that account and change the password.

The Resource Types and syntax for change notifications are available here. For this example, I’m creating a notification for changes to user objects in Azure AD. We will need to create an Azure AD Application Registration that contains Microsoft Graph permissions to read Subscriptions (Subscriptions.Read.All) as well as permissions to create Notification Subscriptions. For the /Users objectClass that is User.Read.All.

The permissions required for notification subscriptions by resource type (e.g. Users, Groups) is detailed here.

On your Azure AD Application Registration select API permissions => Add a permission => Microsoft Graph => Delegated permissions => Subscription.Read.All & User.Read.All=> Add permissions.
Then select the Grant admin consent for <your directory name>.
Record the ClientID (ApplicationID) of the Azure AD Application Registration as well as the TenantID of the Azure AD Directory.

Azure AD Change Notification Azure PowerShell Function Microsoft Graph Permissions

We can now use PowerShell to authenticate to Azure AD using the User Account created earlier as well as leveraging the registered AAD application and its permissions. As mentioned at the beginning Azure AD Change Notifications Subscriptions have a lifetime. For the /Users objectClass that is 3 days. We will create a subscription for 1/2 of that lifetime and maintain it with another function that we will create shortly.

To automate the creation of the subscription (and later the updates to extend the lifetime) using delegated permissions we need to use both the AAD User and the AAD Registered App we created above. Update the following script to provide your Azure AD ApplicationID, Client Secret, TenantID, AAD User UPN and Password. On Line 24 also provide the URL to your Azure PowerShell Function.

The Azure AD Change Notification Subscription is now created. Take note of the ClientState and the Id values. The Id will be configured in the notificationSubscriptionID variable and the ClientState will be configured in the clientStateValue variable in our Function App Settings. These are used to validate notification messages and update the notification subscription.

Azure AD Change Notification Subscription creation
  • when creating a subscription, it will respond with a request to the URL you’ve specified with a code and validationToken. You need to reply to the to request with a HTTP 200 response and the validationToken. The Function we deployed above will do this.
    • the request that includes the validationToken looks like this
{
"code": "Iy8bpuOOiLwVtW........QoxBntTbXGQ==",
"validationToken": "Validation: Testing client application reachability for subscription Request-Id: 9bcab39f-e09d-44fc-9a39-a65934673111"
}

Change Notification Email

To test that our function is working correctly, update an Azure AD User account or create a new one in the Tenant. Within a few minutes you should receive an email (via SendGrid) with the details.

Azure AD Change Notification email

Subscriptions Management Azure Function

Now that we have shown we can receive notifications for our change notification subscription we need to manage the subscription on-going so that we can continue to receive subscription notifications. To do that we will create a Trigger PowerShell Azure Function. It will renew the notification subscription.

From the VSCode Project create a new Azure Function. This time make it:

  • Type: Timer trigger
  • Name: NotificationSubscriptionMgmt
  • Schedule: 0 0 */12 * * *

Open the run.ps1 file for the new Azure Function and replace the script with the following contents.

Edit the local.settings.json to add entries with your values for the clientState, Notification Subscription ID, AAD App Client ID, AAD App Client Secret, AAD Tenant ID, AAD User Account and Password.
This is storing the credentials and configuration in the Azure Function Application settings. It is highly recommended that you store these in an Azure Key Vault and retrieve them at runtime from their using Managed Identity. This post has the details on how to do that.

Azure AD Change Notification Azure AD Query

Deploy the Azure Function App to publish the changes along with the new NotificationSubscriptionMgmt Azure Function.

In the PowerShell session you used to create the Azure AD Change Notification you can use the following snippet to query the subscription. On the schedule (every 12 hours) you will see it update. Update the snippet for the ID of the change notification subscription you created.

$usersNotificationSubscription = Invoke-RestMethod -method GET -uri "https://graph.microsoft.com/v1.0/subscriptions/yourNotificationSubscriptionID"
-Headers @{Authorization = "Bearer $($delegatedToken)"; "content-type" = "application/json"}
$usersNotificationSubscription.expirationDateTime
$usersNotificationSubscription.clientState

Azure AD Query Azure Function

The final Function App. The one that will do something with the change notification. For this example, I’m just going to query Azure AD for the User object that we got the change notification for and get the full user object and their group memberships, then send those details in an email (again via SendGrid).
For a production implementation you would look to use Microsoft Graph Delta Queries to get more granular and efficient with the changes.

Create another Azure Function in your Function App with the configuration;

  • Type: HTTP trigger
  • Name: AzureADQuery
  • Authorization Level: Function

Right click the new Azure Function and select Add Binding. Add an output binding for SendGrid just as we did earlier for the first Azure PowerShell Function. Deploy the Azure Function again. When complete right click on the new Azure Function and copy the Function URL. Update the run.ps1 script file for the ReceiveChangeNotification Function to include the following section at the bottom. Update the snippet with your new Function URL.

# Call the AD Query Function to retrieve objects and perform required logic.
if ($objNotification.clientState -and $objNotification.resource) {
write-host "FORWARDING: Sending change notification details to AzureADQuery Function"
Invoke-RestMethod -Method Post -Uri "https://yourAzureFunctionApp.azurewebsites.net/api/azureadquery?code=spl3I...yourFunctionCode...g%3D%3D"
-headers @{"Content-Type" = "application/json" } `
-body "clientState=$($objNotification.clientState)&updatedObject=$($objNotification.resource)"
}

Edit the run.ps1 file for the new Azure PowerShell Function and replace the script with the following contents.

It will receive from the object that has changed and the clientState from the ReceiveChangeNotifcation Function and then query Azure AD to get the user object. If the object is a Guest account it will also get the group memberships then send a summary email via SendGrid.
Deploy the Azure Functions again to publish them to Azure.

With our third Azure Function deployed when we update an Azure AD Guest User Object we get two emails. One email when the change notification is received, and the second email containing the results of the Azure AD query.

Azure AD Change Notification Summary emails

Summary

In the post I’ve shown how to

  • create an Azure AD Change Notification Subscription
  • building a series of Azure Functions to validate, acknowledge and renew change notification subscriptions and events
  • process notifications to query Azure AD for more information and send a notification with the details.