Environment Security Group Sync Validator
Scheduled flow compares Azure AD security group memberships against Power Platform environment role assignments retrieved via the Admin API (HTTP). Flags users who have environment access but are no longer in the corresponding security group, and vice versa. Sends an Outlook reconciliation report to the security team.
Provided as-is, without warranty of any kind. Review and test each pattern in a non-production environment before deploying it to live automations. See our Terms.
Overview
Weekly governance flow that reconciles Azure AD (Microsoft Entra ID) security group membership against Power Platform environment role assignments. Discrepancies are logged to a Dataverse audit table and delivered as a styled HTML report to the security team so stale access can be revoked and missing access provisioned on a regular cadence.
Use Case
Power Platform environments are commonly "gated" by an Azure AD security group whose members are expected to also hold an environment role (e.g., Environment Maker, System Customizer, System Administrator). Over time, two kinds of drift accumulate: users added to the group but never granted a role, and users who keep a role after being removed from the group. This flow detects both cases every Monday at 08:00 UTC, writes each discrepancy as a row in the flowlibs_securitysyncreports Dataverse table, and emails a consolidated report to the Security Admin. The report is only sent when discrepancies exist, so a clean week produces no noise.
The flow is ideal for teams that:
- IT admins enforcing Azure AD-based access governance on Power Platform environments
- Security teams that need a weekly reconciliation of group membership vs. environment role assignments
- Organizations auditing for both missing and stale environment access
- Tenants logging discrepancies to a Dataverse audit table for compliance review
Flow Architecture
Recurrence_Weekly_Monday
RecurrenceRuns weekly on Monday at 08:00 UTC.
Init_var* (x13)
InitializeVariable (parallel)Thirteen Initialize Variable actions populate eight env-var-backed strings (varTenantId, varClientId, varClientSecret, varAdminEmail, varTargetEnvironment, varSecurityGroupId, varReportTable, varSecurityAdminEmail) plus five working variables (varGroupMemberEmails array, varEnvUserEmails array, varDiscrepancyHtml string, varDiscrepancyCount integer, varCheckTimestamp string = utcNow()).
Get_Group_Members
Microsoft Entra ID GetGroupMembersReturns all members of the configured Azure AD security group.
Get_Env_Role_Assignments
HTTP GET (ActiveDirectoryOAuth)Calls the Business Application Platform admin API at /providers/Microsoft.BusinessAppPlatform/scopes/admin/environments/{envId}/roleAssignments?api-version=2020-10-01 to return every principal assignment on the environment.
Parse_Env_Role_Assignments
ParseJsonTypes the BAP role-assignments response for downstream actions.
Select_Group_Member_Emails
SelectProjects each Entra group member into a flat, lowercased email string.
Set_varGroupMemberEmails
SetVariablePersists the projected group-member email array so the diff can use contains() without re-querying.
Filter_User_Principals
Query (Filter Array)Keeps only role assignments where principal.type == 'User', removing service principals and group assignments before the comparison.
Environment Variables
| Schema name | Type | Default | Description |
|---|---|---|---|
| flowlibs_GraphTenantId | String | <your-tenant-id> | Tenant ID used for ActiveDirectoryOAuth authentication on the BAP admin API. |
| flowlibs_GraphClientId | String | <configure> | App-registration client ID used to obtain a token for the BAP admin API. |
| flowlibs_GraphClientSecret | String | <configure> | App-registration client secret used to obtain a token for the BAP admin API. Store in a secure vault and reference here. |
| flowlibs_AdminNotificationEmail | String | admin@yourcompany.com | General admin notification address (reserved for future use). |
| flowlibs_TargetEnvironmentName | String | <configure> | Environment ID to evaluate (e.g., default-<your-tenant-id> or a specific environment GUID). |
| flowlibs_SecurityGroupId | String | <configure> | GUID of the Azure AD security group expected to gate access to the environment. |
| flowlibs_SecurityReportTable | String | flowlibs_securitysyncreports | Entity set name of the Dataverse discrepancy log table. |
| flowlibs_SecurityAdminEmail | String | security-admin@yourcompany.com | Recipient of the weekly reconciliation report. Use a semicolon-delimited list for multiple recipients. |
Connectors & Connections
| Connector | API name | Actions used |
|---|---|---|
| Microsoft Entra ID | shared_azuread | GetGroupMembers (Reads members of the configured security group) |
| Microsoft Dataverse | shared_commondataserviceforapps | CreateRecord (Writes discrepancy rows to flowlibs_securitysyncreports) |
| Office 365 Outlook | shared_office365 | SendEmailV2 (Sends the High-importance reconciliation report) |
| HTTP (built-in) | http | HTTP (GET against the BAP admin API using ActiveDirectoryOAuth) |
Note — All connections are referenced as solution connection references; the flow is portable between environments as long as a connection is mapped at import time.
Customization Guide
Almost every realistic variant of this flow can be implemented by changing environment variable values. A few cases require small edits inside the flow definition — those are called out explicitly below.
- Target a different security group
- Update flowlibs_SecurityGroupId with the new group GUID. No flow edit needed.
- Validate a different environment
- Update flowlibs_TargetEnvironmentName with the destination environment ID. The BAP admin API path is composed from this variable, so nothing else changes.
- Change cadence
- Edit the Recurrence_Weekly_Monday trigger (Weekly to Daily, or change weekDays/hours). The rest of the flow is cadence-agnostic.
- Route the report elsewhere
- Update flowlibs_SecurityAdminEmail. For multiple recipients, change to a semicolon-delimited list (Outlook SendEmailV2 accepts a@x.com;b@x.com).
- Add additional role types
- Replace the Filter_User_Principals expression equals(item()?['properties']?['principal']?['type'], 'User') with an or() that also matches 'Group' or 'ServicePrincipal' if you need to audit non-human access (also remove the email filter since service principals have no email).
- Ignore known service accounts
- Add a second condition inside each If_*_Not_In_* that rejects a hardcoded allow-list, e.g., and(not(contains(...)), not(contains(createArray('svc1@x.com','svc2@x.com'), email))).
- Increase group size support
- Entra GetGroupMembers is paged; if you expect more than 999 members, swap to an HTTP call to Graph /groups/{id}/transitiveMembers and iterate @odata.nextLink in a Do until loop.
Key Expressions
The flow is intentionally light on Power Fx / WDL gymnastics — the heaviest expressions are the branch-name concatenation and the approval outcome check. They are listed below in the order they appear in the flow.
EXPR.01Normalize Entra member email
Normalize an Entra member to a lowercased email, falling back to UPN when mail is null (guest/B2B accounts).
EXPR.02Normalize BAP principal email
Normalize a BAP role-assignment principal to a lowercased email; missing emails (service principals) are skipped by the non-empty guard downstream.
EXPR.03Parameterized BAP admin URL
Parameterized BAP admin URL so the same flow works against any environment.
EXPR.04Forward membership diff
The forward membership diff; the mirror direction uses varGroupMemberEmails.
EXPR.05Report gate
Gates the report action so clean weeks are silent.
EXPR.06User-principal filter
Restricts the comparison to human users so service principals and group assignments are not falsely flagged.
Comments
Sign in to join the conversation.
Sign inNo comments yet. Be the first to share your experience with this flow.