I was talking to my friend Sumit Rai, and he asked me how he could apply what we learnt from my previous article (https://www.andresbiarge.com/post/sync-users-between-aad-dataverse) so that users added to an Azure AD / Microsoft Entra ID Security Group are synced automatically and on demand to a Power Platform environment.
Note: in the article, we saw how we can sync users in bulk. Just before the Go Live of a big application. But when your application has been live for some time, you'll going to have joiners who will need to be synced automatically to overcome any adoption obstacles.
So, we agreed to look at the matter separately and meet each other once we'd come to a solution. And we both came up with two different approaches:
Using Microsoft Graph Webhooks to get notified whenever a user is assigned to the Security Group.
Using Microsoft Graph Delta Queries to ask for the last changes in a polling fashion.
The difference between the two approaches is that of a Polling vs Event-driven architecture.
In this article, I will walk you through my learnings trying out both approaches.
Enough talking, let's go already 💪.
YouTube Companion Video
I've created this video so that you can have a more guided step by step guide and where I show you how to test this out. Check it here:
Approach #1 Using Microsoft Graph Webhooks
With this pattern, you will need to create a Listener / Webhook to receive any changes in the Security Group that secures your Power Platform environment. The quickest, cheapest and easiest way to do that is via an Azure Function. For this example, I decided that I didn't want to invest much on securing the Azure Function (certificates/client-secrets/etc.), so the syncing between Azure Entra ID and Dataverse is done via a Power Automate cloud flow.
Documentation you'll need
To have a deeper understanding of this approach, I recommend you read through the articles below:
General guide to Webhooks and what are the Graph resources that support Webhooks: Set up notifications for changes in resource data. - Microsoft Graph | Microsoft Learn
Design considerations when implementing Webhooks (throttling, subscription "handshake", etc.): Receive change notifications through webhooks - Microsoft Graph | Microsoft Learn
PowerShell Webhook Graph Library Documentation: Microsoft.Graph.ChangeNotifications Module | Microsoft Learn
Prerequisites
Before explaining how to set up all the resources, you will need:
Power Platform:
Environment you can set fire to safely.
Secure the environment with a Security Group.
System Administrator security role.
The ability to create Power Automate cloud flows.
Azure:
A Security Group you are the owner of.
An Azure Function App that you can set fire to.
Steps order
For your ease to reproduce the case, I recommend you follow this order:
1. Create your Power Automate Flow.
2. Create your Azure Function (recommendation for performance/consumption: PowerShell on top of Linux).
3. Register the Webhook into Microsoft Graph.
Setting up the Webhook
Import-Module Microsoft.Graph.ChangeNotificationsConnect-MgGraph -Scopes "User.Read.All","Group.Read.All"$params = @{ changeType = "updated" notificationUrl = "The URL of your Az. Function" resource = "/groups/{your-group-guid}/members" expirationDateTime = [System.DateTime]::Parse("<up to 29 days>") clientState = "<your secret for the Graph subscription>"}$newGraphSubscription = New-MgSubscription -BodyParameter $params$newGraphSubscriptionDisconnect-MgGraph
You'll get a reponse that will look like the following:
$newGraphSubscription | ConvertTo-Json
Creating the Az. Function
Nowadays, my preferred language for these tasks is PowerShell. It's great and a no brainer to set it up in your environment + runs in any machine.
Here's a template snippet you can use to test the Webhook:
using namespace System.Netparam($Request, $TriggerMetadata)#First, we detect if this is the validation requestif ($Request.Query.validationToken) { $responseBody = [uri]::UnescapeDataString($Request.Query.validationToken) Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK ContentType = "text/plain" Body = $responseBody }) }else { #else,means we got the actual message foreach ($groupChange in $Request.Body.value) { if (-not $groupChange.resourceData."members@delta"."@removed") { #only call Flow when it's a new user. $webRequestParams = @{ Uri = "<the URL of your HTTP triggered Power Automate Flow>" Method = "Post" ContentType = "application/json" Body = [PSCustomObject]@{ clientState = "$($groupChange.clientState)"; newUserId = "$($groupChange.resourceData."members@delta".id)"; } | ConvertTo-Json } Invoke-WebRequest @webRequestParams } } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK ContentType = "text/plain" Body = "Ok" }) }
⚠Warning⚠: Beware of your Webhook's lifecycle management
Webhooks configured to receive notifications from the /groups segment can only last for 29 days max. You can check this limit directly from the documentation here: subscription resource type - Microsoft Graph v1.0 | Microsoft Learn
This means you will need to setup a process to renew the subscription: Receive change notifications through webhooks - Microsoft Graph | Microsoft Learn
It's 5 lines with PowerShell:
Import-Module Microsoft.Graph.ChangeNotifications$params = @{ expirationDateTime = [System.DateTime]::Parse("<your next renewal datetime>")}Update-MgSubscription -SubscriptionId $subscriptionId -BodyParameter $params
Approach #2 Using Microsoft Graph Delta Queries
With this pattern, you'll be polling Microsoft Graph for changes on the /groups segment of the Graph API. Bravo Microsoft here, because the way it works comes very handy:
You'll first need to perform a first request to the /delta Graph API endpoint of the /groups segment. You will need to use an OData filter to get the information from the group(s) you are interested in.
Graph will respond with the changes (added members or removed ones). Because of Graph's pagination capabilities, you will need to implement a "follow through" logic: every time you see an "@odata.nextLink", use that value to perform the next request in a recursive manner.
When you see that Graph's response doesn't include an "@odata.nextLink", you will se an "@odata.deltaLink". Store this value into persistent storage (e.g., Dataverse table). This link is the one you will need to use the next time you want to get the changes on the security group(s). This "@odata.deltaLink" stores the actual state where you left off, so continuing from there will ensure you get the real next changes on the resource.
A possible solution here is that you setup a recurrent Power Automate cloud flow that performs this polling logic with the "@odata.deltaLink" every day at 5 AM.
You can check out the actual implementation in my YouTube companion video.
Enterprise Level Considerations
These two approaches are great for a single PROD environment scenario. But, what happens when we want to create a more scalable solution that works for enterprises with several big solutions that are already in PROD?
My friend Sumit and I discussed possible solutions for that, and we concluded that setting up an Azure Event Hub in the middle would be a great fit. Of course, this is already a recommended Microsoft approach: Receive change notifications through Azure Event Hubs - Microsoft Graph | Microsoft Learn.
Conclusion
I hope the problem statement and solution was clarifying for you. I tried to explain how to approach the automatic and proactive syncing between new members of an Azure Entra ID Security Group (good old Azure Active Directory) into a Dataverse environment.
Please, write any feedback, questions and requests for new content in the comments section below. Also, be sure to check the YouTube companion video and the rest of the content I'm trying to curate for you both in my Blog and YouTube channel.
See you around 💪
Andres
コメント