Hi Microsoft 365 devs !
Here we go again with the part III of my blog post series about Microsoft Graph Subscriptions (aka Webhooks).
In this post, we will see how we can implement a real world solution that handles the lifetime of our Microsoft Graph subscriptions !
What’s the matter with subscription lifetime ?
Microsoft Graph subscriptions are great to hook our custom logic triggered by the events that occur in our corporate information (groups, users, events, and so on…). However, as we have seen in part I & II of this series, the subscriptions we create expire after quite a small amount of time. This expiration allows Microsoft Graph to spare execution cycles that may, in fact, be totally useless since the remote system may no longer use them!
So, as long as we want our custom solution to handle properly the notifications and keep receiving them, we need to implement some logic that will allow us to do so…
It requires a bit of a significant effort to achieve something that might seem trivial, but it is probably also a way to ensure that, we, third parties, are not overwhelming Microsoft Graph without caring what we do without a very good reason to do it.
Let’s architect a viable solution !
In the previous part, we saw how to implement our Webhook as a pro with Azure Functions using TypeScript and VS Code. We will rely on these foundations to make sure that the lifetime of our subscription are properly extended anytime it is needed.
Concretely, it means that, whenever a notification is sent to our system, the Webhook will not only trigger our custom business logic to be executed, but will also update the subscription to postpone the date of its expiration.
In order to respond as fast as possible to Graph (as expected from Webhook endpoints), we will delegate the tasks to other functions that gets executed when a message is pushed into a queue.
Let’s see how the code of our Webhook will look like
As we can see at line 60, we push the subscription id to a queue, an other Azure Function we call EnsureSubscription will be triggered and will be responsible for updating the expiration date of the subscription. Another queue-triggered Function we call BusinessTask will achieve what we really want to do with the notification.
Setup some prerequisites
- It will use Microsoft Graph to update the subscription expiration. Thus, it needs to be allowed to. It will do it in an unattended manner. If you follow me, it means we need to register an AAD application and grant it the needed application permissions
- To implement the calls to Microsoft Graph in the easiest possible way, I will use the awesome PnP JS library (however, that is not strictly required)!
Register the AAD application
From the Azure AD portal, go to the App registrations section and click Add new registration
Select a name and register the new application.
We’ll need to grant our application the required permissions, From the API Permissions section, add the following permissions to Microsoft Graph, don’t forget to grant admin consent once you set the API permissions
We will put the needed AAD app information in our function app settings. At development time, it will be in the local.settings.json file that should probably already exist in your solution. If it does not, you can create it with the keys we’ll set the values later on
- Copy the Directory (tenant) Id and paste it as the value of the TENANT app setting
- Copy the Application (client) Id and paste it as the value of the AAD_APP_ID app setting
Since our Azure Function will use Microsoft Graph in an unattended manner (without any user interaction), We will use client credentials authentication flow, we then need to generate a client secret.
From the Certificates & secret section, click the New client secret button to generate a new one, copy the generated secret as the value of the key AAD_APP_SECRET in your app settings. Make sure to copy it immediately, because you will never be able to see it again.
Note: I’ll use a client secret it to avoid bring more complexity to the steps here. It is actually recommended that you rather use client certificates instead which are safer.
Install the dependencies
As mentioned above, we will use PnP JS, we will also need to use the Azure Storage API. In a console in your solution folder, type the following command
npm install @pnp/graph-commonjs @pnp/logging @pnp/nodejs-commonjs azure-storage –save
Tip for development time
Since we are now doing much more development, it will probably be easier to run our azure function locally, it will be much easier to debug ! But As you probably understood by now, Microsoft Graph subscriptions need to access a public URI. That’s most likely not the case of our development web server. We will use ngrok to redirect a public facing URL to our local development machine. We’ll need to download the small tool. Run the tool as such
ngrok http 7071
Note that 7071 is the http port used by the development Azure Function runtime, you’ll probably have to make sure it is the same port used in your local runtime, but it is most likely the case !
We will then need to copy the ngrok public URL to our app settings
Copy the https URL as the value of OVERRIDDEN_WEBSITE_HOSTNAME app setting.
Configure the Microsoft Graph Resource to subscribe to
In the app settings, we’ll also need to specify the resource we want to subscribe to, concretely, it means set the Microsoft Graph relative URL of the resource. For instance, here I will subscribe to the events of my user, I will need to specify it using the user id like “users/efda010b-19a4-4303-bf7b-70bd6e62e7d2/events“, since we will use Microsoft Graph using an app-only token, we cannot use “me/events“
Let’s create a few helpers
In a helpers folder:
create a configure.ts with the following code
create a file date-helper.ts
create a file azure-queue-helper.ts
create a file subscription-helpers.ts
and a last one subscription-setup.ts
Let’s code the EnsureSubscription function
First of all, let’s create a new queue-triggered function, from VS Code Azure extension, click the “Create function” button.
Select “Azure Queue Storage Triggered” and name it “EnsureSubscription“.
Select the app setting you want to use to store the Azure Storage connection string. You will have to put the connection string to your storage account. The VS Code extensions also allows to create a new storage account if you don’t already have one.
Name the azure queue as “subscriptions-to-ensure“
Then let’s write the following code as the body of our EnsureSubscription function
Let’s code the Business Task function
We will create another queue-triggered Azure Function that will execute the actual task we want to achieve whenever a notification is received. Here my business task will be to simply log the Id of the updated event. It is here that you will have to change to code to achieve the task according to your requirements.
Repeat the steps above to create a new queue-triggered Function and name the queue “business-tasks”. Just for information, the code I have in this function is
This custom task will grab the updated event and update its subject if it is not already properly updated (This will avoid resulting in an infinite loop!).
Let’s test it
We should be good now, hit F5 in VS Code to run your Azure Function locally ! We will then need to trigger the subscription to be created ! To make sure all is going well, set a breakpoint in the EnsureSubscription index.ts file at line 32
From the Azure Portal, access your storage account, select the subscriptions-to-ensure queue and add a new message with any dummy content
Your breakpoint should be hit almost at once. Continue the execution and your subscription should be added. To test your webhook solution, set a breakpoint in your BusinessTask function and then add an event to the calendar of the user you specified (or do any action that should triggered the specific notification you requested).
In my case, I create or update an event in my calendar (It is the resource monitored by the Microsoft Graph Subscription), and I see it gets updated after a few seconds
Now time to deploy !
If everything in debug works as expected you can now deploy the Azure Function to Azure, in the VS Code extension tab, click the “Deploy to Function App..” button. Then you will have to make sure you copy the following app settings to the Function App
We now have a solution that will keep our subscriptions alive as long as it is used. Take care however, with this solution as is, if there is no activity during the maximum lifetime of your particular subscription, it will get deleted ! Fortunately, with the solution described in this post, the only thing you will have to do is to setup a time triggered function that will trigger the EnsureSubscription, But that will be part of a next post 🙂
You can get the whole solution on the GitHub repo here
Hope you liked it and please let me know any feedback you might have !